10.25.2017 考试总结与解题报告

来源:互联网 发布:2016淘宝年会 马云 编辑:程序博客网 时间:2024/06/07 12:29

这次考试题目难度不是很大,但考得并不是很好,主要原因有仨:

①性情浮躁

这次考试第一题我是完全有能力A的,但是就是因为一个to数组严格代表什么,该运用到那些方面去没有仔细分析,过了样例就没管了,结果到考试前五分钟拍出n多错误,那时已经没时间调试了,其实那时候还可以用几十秒把我的暴力对拍程序改改文件名交上去,暴力分有五十分啊ヽ(ˋДˊ)ノ那也不至于这题爆零(我是唯一这题爆零的,大家都起码交了一个暴力50分),还有,现成的暴力对拍程序放在那,我只要改一改文件名就行了啊(这可能是OI史上唯一一次暴力对拍程序比本体分高的了)

② 不注意数据范围

这次考试第二题的俩输入变量是1e12的范围,由于一推出公式就得意忘形了,只开了个int,long long都没开,结果丢了70分

③缺乏缜密思考的能力

其实我在做第三题时,思想与正解有很长一段的交集,但最终没有继续,原因是因为不敢用这种感觉上比较偏的方法,说明我的思考证明能力以及自信心都有所不足

综合这次考试反应出了我的很多弊病,我应该逐步改正,防止这成为我NOIP无法上一等的原因

下面附上这次考试的解题报告

Task pudding

【题目描述】

现在有 n 个布丁排成一排,每个布丁都有一个正整数颜色。

有 m 个操作:

第一种操作 1 x y 将所有颜色为 x 改为颜色 y。

第二种操作 2

询问当前有多少段颜色。

【输入数据】

第一行两个正整数 n,m。

下面一行,n 个正整数,表示一排的布丁。

下面 m 行,每行为一个操作。

【输出数据】

输出每组询问的答案。

【输入样例】
4 3
1 2 2 1
2
1 2 1
2
【输出样例】
3
1
【数据约定】
50%
n,m <= 2000
100% n,m <= 100000,颜色均不超过 10^6

题目概要

这题原型其实就是HNOI-2009的梦幻布丁,题意是给出一条序列,每次询问其中有多少段或将序列中所有的A变为B

思路

50分解法

50分解法非常好想 ,其实就是暴力更改查询,然而这小学生都该拿的分就我没拿

对于50分暴力的优化

目前我能想到的优化只有两个:
一是线段树解法,就像维护区间最长连续串儿一样维护
二是用链表,每次保证遍历的每一次都不会浪费,最后把俩链连起来就行了

但是法一不推荐,代码难打复杂度高

100分解法

为什么要写“对于50分暴力的优化”,还是自有它的原因,就像NOIP2016天天爱跑步一样,都是由部分分“进化”而得正解

这题也是一样,楼上法二只要优化一点点便可AC

首先让我们分析一下为什么法二会TLE,对于最坏复杂度(不管是一名OIER 还是cy的学生 都要感受数据的恶意,不能乐观,往往正解是保证最坏复杂度也能A的),每一次修改最坏O(n),每一次都不询问,都是修改,总复杂度O(nm),在数据是1e5的情况下是绝不可能AC的

那么我们应该加一些优化,降复杂度为O(nlog2m)O(mlog2n)

这题我们分析一下,O(m)的复杂度地位基本不能修改,所以我们从O(n)动刀

分析得出最坏复杂度发生在将长度为n的链表连到长度为0的链表后边儿,然而根据这种情况,我们很容易看出把0接到n后边复杂度更优

我们引进启发式合并的概念

把小集合往大集合上合并称为启发式合并,这样的预估复杂度为O(log2n)
复杂度证明: 一个元素从一个集合被加入另一个集合时, 所在集合的规模至少扩大一倍. 如果没有分离操作且元素数目有限, 合并的次数是O(log2n)的.

发现这样做好像复杂度变为O(mlog2n)了,可以放心AC

等等,突然间发现对拍停住了,看看数据,发现有一种情况会挂:

当我们把1变为3 , 2变为3时,由于3的个数是零,所以把3连到了1,又把3连到了2,但其实3和2应该合并的

万能的神奇海螺告诉我们,可以用一个to数组记录每个颜色实际表示的啥呀,比如to[a]=b表示a颜色实际上是b

然后就可以真正地AC了

蒟蒻瑟瑟发抖的代码

#include<bits/stdc++.h>#define rg registerusing namespace std;template <typename _Tp> inline void read(_Tp &x){char c11=getchar();x=0;    while(c11<'0'||c11>'9')c11=getchar();while(c11>='0'&&c11<='9'){x=x*10+c11-'0';c11=getchar();}}const int maxn=2000005,maxm=4000001;int n,m,ans=0;int P,p,x,y;int a[maxn],to[maxm],b[maxn];int head[maxm],nt[maxn];int r[maxn],tot[maxm];void init();void work(){    for(int t=1;t<=m;++t){        read(P);        if(P==1){            read(x);read(y);//            x=b[lower_bound(b+1,b+n+1,x)-b];//            y=b[lower_bound(b+1,b+n+1,y)-b];            if(to[x]==to[y])continue; //注意以下代码都是带to数组的,应为to的下标已无任何意义            if(tot[to[x]]>tot[to[y]])swap(to[x],to[y]);          //启发式合并的神来之笔            for(int i=head[to[x]];i;i=r[i])                      //动态维护ans                ans-=(a[i]!=a[i-1])+(a[i]!=a[i+1]);              //左右相同减一或二            for(int i=head[to[x]];i;i=r[i])a[i]=to[y];            p=0;            for(int i=head[to[x]];i;i=r[i])                ans+=(a[i]!=a[i-1])+(a[i]!=a[i+1]),p=i;          //更改后与其不同,加一            if(p)                r[p]=head[to[y]],head[to[y]]=head[to[x]];        //这三行都是合并操作            head[to[x]]=0;            tot[to[y]]+=tot[to[x]],tot[to[x]]=0;        }        else            printf("%d\n",ans);    }}int main(){//    freopen("pudding.in","r",stdin);//    freopen("pudding.out","w",stdout);    init();    work();    return 0;}void init(){    read(n);read(m);    for(rg int i=1;i<=maxm;++i)to[i]=i;    for(rg int i=1;i<=n;++i)        read(a[i]);//b[i]=a[i];//    sort(b+1,b+n+1);int kl=unique(b+1,b+n+1)-b;   //本想离散化的//    for(rg int i=1;i<=n;++i)a[i]=b[lower_bound(b+1,b+n+1,a[i])-b];    for(rg int i=1;i<=n;++i){        ans+=(a[i]!=a[i-1]);        r[i]=head[a[i]];        head[a[i]]=i;        ++tot[a[i]];    }}

Task prison

【题目描述】

现在有一个长度为 n 的序列,请你给它的每一个位置都染上一个 1~m 的颜色,使得至

少有一个相邻的格子颜色相同,问有多少种方案。

答案请 mod 100003。

【输入数据】

两个正整数m,n。

【输出数据】

输出方案数。

【输入样例】
2 3
【输出样例】
6
【数据约定】
30%:n <= 50,m <= 1000
100%:n <= 10^12,m <= 10^8。

题目还需要概要吗?

以HNOI2008越狱为原型

思路

10分解法

直接深搜

30分解法

由我们丰富的数学知识可知总共有mn种方案,其中有m(m1)n1中方案不会发生越狱,两两一减即可

100分解法

用上快速幂,还有……

开long long

开了long long的代码

这里写代码片#include<bits/stdc++.h>using namespace std;const long long mod=100003;long long n,m;long long qpow(long long A,long long B){    long long ans=1;    while(B){        if(B&1)ans=(ans*A)%mod;                     //多mod是好事        A=A*A%mod;        B>>=1;    }    return ans;}void work(){    long long fir=qpow(m,n);    long long sec=(m*qpow(m-1,n-1))%mod;    while(fir<sec)fir+=mod;    printf("%lld\n",(fir-sec)%mod);    return ;}int main(){    freopen("prison.in","r",stdin);    freopen("prison.out","w",stdout);    scanf("%lld %lld",&m,&n);    work();    return 0;}

Task SPFA

【题目描述】

给你一张含有 n 个点 m 条边的联通无向图,记录 1 号点到每个点的最短路长度,询问去掉与 i 号相邻的所有边后,1 号点到多少个点的最短路长度改变,若不连通则也视为改变。

【输入数据】

第一行两个正整数 n,m,

接下来 m 行,每行三个正整数数 i,j,k,表示一条边< i , j >,长度为 k。

【输出数据】

N 行,第 i 行表示去掉与 i 号相邻的所有边后,1 号点到多少个点的最短路长度改变。

【输入样例】
2 1
1 2 1
【输出样例】
1
1
【数据约定】
30% n <= 100,m <= 300
100% n <= 5000,m <= 20000,边权均为不超过 100 的正整数。

题目概要

求对于每一个点i,是1到多少个点的最短路必经结点

思路

30分解法

不断求最短路,看看有多少个点最短距离改变

100分解法

删去那些不是任何最短路上的点,再枚举每一个点,将其入队,将其所有指向点入读减一,入度为零的点入队,最后入队数量就是ans

Atention:当删去的点为一时输出ans-1

因为删掉点1后,1到1的距离从0变成了0

代码

#include<bits/stdc++.h>using namespace std;#define rg register#define cl(x) memset(x,0,sizeof(x))#define cl1(x) memset(x,-1,sizeof(x))template <typename _Tp> inline void read(_Tp &x){char c11=getchar();x=0;while(c11<'0'||c11>'9')c11=getchar();while(c11>='0'&&c11<='9'){x=x*10+c11-'0';c11=getchar();}}const int maxn=5050,maxm=20020;int n,m;struct node {int v,w,nxt;} a[maxm<<1];int head[maxn],p=0;int dis[maxn];int tot[maxn],oto[maxn];struct node1 {int v,w,nxt;} b[maxm<<1];int head1[maxn],p1=0;void init();inline void add(int,int,int);inline void add1(int,int,int);void spfa(){    queue <int> q;    q.push(1);    cl1(dis);    dis[1]=0;    while(!q.empty()){        int x=q.front();        q.pop();        for(rg int i=head[x];i;i=a[i].nxt)if(dis[a[i].v]==-1||dis[a[i].v]>dis[x]+a[i].w)dis[a[i].v]=dis[x]+a[i].w,q.push(a[i].v);    }    return ;}void ex(){                                             //重新构图函数    cl(head1);cl(tot);    for(rg int i=1;i<=n;i++)    for(rg int j=head[i];j;j=a[j].nxt)    if(dis[i]+a[j].w==dis[a[j].v])                 //若这条边是最短路一部分    add1(i,a[j].v,a[j].w);}void work(){                                             //开始队列操作    for(rg int i=1;i<=n;i++){        for(rg int j=1;j<=n;j++)oto[j]=tot[j];        queue <int> q;        q.push(i);        int ans=0;        while(!q.empty()){            int x=q.front();            q.pop();++ans;            for(rg int j=head1[x];j;j=b[j].nxt){--oto[b[j].v];if(!oto[b[j].v])q.push(b[j].v);}        }        if(i==1)printf("%d\n",n-1);                   //不加全WA        else        printf("%d\n",ans);    }    return ;}int main(){                     freopen("problem.in","r",stdin);freopen("problem.out","w",stdout);    init();    spfa();    ex();    work();    return 0;}void init(){    read(n);read(m);    int A,B,C;    cl(head);    for(rg int i=0;i<m;++i){        read(A);read(B);read(C);        add(A,B,C);add(B,A,C);    }    return ;}inline void add1(int u,int v,int w){                        //重新构图    b[++p1].v=v;    b[p1].w=w;    b[p1].nxt=head1[u];    head1[u]=p1;    tot[v]++;}inline void add(int u,int v,int w){    a[++p].v=v;    a[p].w=w;    a[p].nxt=head[u];    head[u]=p;}