这次考试题目难度不是很大,但考得并不是很好,主要原因有仨:
①性情浮躁
这次考试第一题我是完全有能力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
(
n
)
,每一次都不询问,都是修改,总复杂度
O(nm)
O
(
n
m
)
,在数据是
1e5
1
e
5
的情况下是绝不可能AC的
那么我们应该加一些优化,降复杂度为 O(nlog2m) O ( n l o g 2 m ) 或 O(mlog2n) O ( m l o g 2 n )
这题我们分析一下, O(m) O ( m ) 的复杂度地位基本不能修改,所以我们从 O(n) O ( n ) 动刀
分析得出最坏复杂度发生在将长度为 n n 的链表连到长度为的链表后边儿,然而根据这种情况,我们很容易看出把 0 0 接到后边复杂度更优
我们引进启发式合并的概念
把小集合往大集合上合并称为启发式合并,这样的预估复杂度为 O(log2n) O ( l o g 2 n )
复杂度证明: 一个元素从一个集合被加入另一个集合时, 所在集合的规模至少扩大一倍. 如果没有分离操作且元素数目有限, 合并的次数是 O(log2n) O ( l o g 2 n ) 的.
发现这样做好像复杂度变为 O(mlog2n) O ( m l o g 2 n ) 了,可以放心AC
等等,突然间发现对拍停住了,看看数据,发现有一种情况会挂:
当我们把1变为3 , 2变为3时,由于3的个数是零,所以把3连到了1,又把3连到了2,但其实3和2应该合并的
万能的神奇海螺告诉我们,可以用一个to数组记录每个颜色实际表示的啥呀,比如 to[a]=b t o [ a ] = b 表示a颜色实际上是b
然后就可以真正地AC了
蒟蒻瑟瑟发抖的代码
#include<bits/stdc++.h>
#define rg register
using 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 n 种方案,其中有 m(m−1)n−1 m ( m − 1 ) n − 1 中方案不会发生越狱,两两一减即可
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 i ,是到多少个点的最短路必经结点
思路
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;
}