总结
其实这次考试的题目并不难,但是却考得比较崩。总结下原因有这么些:
- 思维不够缜密。这也是最重要的,想到非正解思路之后,还没有验证它的正确性,就开始打代码,导致浪费了大把的时间。
- 考试的策略问题。做题顺序的不当,导致了我第二题没拿到该拿的分,尤其需要注意调整,而且打对拍的也占用了较多的时间,三个半小时还是有点不太够用啊。
- 刷题不够,效率较低。我做思维题做的不算多,各个算法、数据结构的各个类型的题目也没有全部覆盖到,正因为刷题的缺少,导致解题思维没有成型,而且效率也比较低,错误比较多。
梦幻布丁(HNOI2009)
Problem Description
详细请戳链接!
偷个懒应该没人会发现吧
Thoughts
第一眼就是线段树,这不明摆着嘛,利用最长连续零的套路,维护左右区间的颜色状态(杂色/Ci色),还有区间最左边最右边的颜色,顺便计数即可。然而,这种轻轻松松就AC的想法还是too naive了。但因为
n,m<=200000
,建树,加更新经常需要递归至最下层,效率比线段树低了很多,而且这题的常数比较大。
后来打完了,回来看这道题,发现可以优化,但是时间不够,开始打对拍。
我想的优化是,因为修改和查询都是对于全区间而言,那么颜色种类数k经过修改只可能变得更少,因为同种颜色必定会同时修改,不可能分为两种或多种颜色。而且既然线段树有较大的可能性要递归到最底层,那么就可以用zkw线段树优化,直接采用链式前向星的思想,修改特定颜色的时候,直接可以查询,并且把修改当做单点修改。
然后蒟蒻就想不到有什么比较好的优化方法了……然后在下考前几分钟草率地打了个对拍,拍了几百组就草率地交了。
期望得分:70 实际得分:50
考后,打了优化的思想,发现也是50分,数据一点梯度都没有啊喂
Solution
事实上,我从第一眼就被误导了,打线段树真个错误的决定。正解要用链表优化和启发式合并。
也就是,修改的时候,并不实际去进行修改,而是做标记,用nc[i]数组表示第i个现在是什么颜色,在读入的时候,就先处理出ans,修改时维护即可。在合并时,将元素较少的颜色合并到元素较多的颜色上。和题目中要求的反了?没关系,标记一下就好了。
Code
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
template <typename Tp> inline void read(Tp &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
const int size=1000010;
int n,m,ans,c[size],nc[size],head[size],tail[size],nxt[size],cnt[size];
void merge(int a,int b)
{
if(!cnt[a])
return ;
cnt[b]+=cnt[a];
for(int i=head[a];~i;i=nxt[i])
{
if(c[i-1]==b)
ans--;
if(c[i+1]==b)
ans--;
}
for(int i=head[a];~i;i=nxt[i])
c[i]=b;
nxt[tail[b]]=head[a];//接起来
tail[b]=tail[a];
head[a]=tail[a]=-1;//将a颜色清空
cnt[a]=0;
}
int main()
{
freopen("pudding.in","r",stdin);
freopen("pudding.out","w",stdout);
int k,u,v;
read(n),read(m);
memset(head,-1,sizeof(head));
memset(tail,-1,sizeof(tail));
for(int i=1;i<=n;i++)
{
read(c[i]);
nc[c[i]]=c[i];
if(c[i]!=c[i-1])
ans++;
if(tail[c[i]]==-1)
tail[c[i]]=i;
cnt[c[i]]++;
nxt[i]=head[c[i]];
head[c[i]]=i;
}
while(m--)
{
read(k);
if(k==2)
printf("%d\n",ans);
else
{
read(u),read(v);
if(u==v)
continue;
if(cnt[nc[u]]>cnt[nc[v]])
swap(nc[u],nc[v]);
merge(nc[u],nc[v]);
}
}
return 0;
}
越狱(HNOI2008)
Problem Description
详细请戳链接!
再偷个懒应该还是没人会发现吧
Thoughts
考试的时候想复杂了,看了很久,觉得有点像组合数学+容斥原理,然后就需要知道连续两次的,减去连续三次,加上连续四次……但是这个常数不好推啊,我暴力+手推,推了两次公式,均被自己推翻。当时内心是崩溃的,而且心态又不是很好,因为先做了第三题(欲知有多惨,见下)……所以推了好久,没推出来,就去打对拍了,没救了,放弃治疗qwq
期望得分:0 实际得分:(已和谐)
Solution
事实上,第二题是最水的……看来只有我没有推出来,被各位神犇暴踩QAQ
考虑补集,首先所有方案是
mn
,如若要没有任意两个相邻方块颜色相同,显然我们有
m∗(m−1)n−1
,那么答案就是两者的差了,注意开long long,打个快速幂再%一下。
Code
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod=100003;
ll n,m;
ll power(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)
res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main()
{
freopen("prison.in","r",stdin);
freopen("prison.out","w",stdout);
scanf("%lld%lld",&m,&n);
printf("%lld\n",(power(m,n)+mod-(m*power(m-1,n-1)%mod))%mod);
return 0;
}
Problem
Problem Description
没找到原题,偷不了懒了……
Description
给你一张含有 n 个点 m 条边的联通无向图,记录 1 号点到每个点的最短路长度,询问
去掉与 i 号相邻的所有边后,1 号点到多少个点的最短路长度改变,若不连通则也视为改
变。
Input
第一行两个正整数 n,m,
接下来 m 行,每行三个正整数数 i,j,k,表示一条边
Output
N 行,第 i 行表示去掉与 i 号相邻的所有边后,1 号点到多少个点的最短路长度改变。
Sample Input
2 1
1 2 1
Sample Output
1
1
Data Size
30% :
n<=100,m<=300
100% :
n<=5000,m<=20000
,边权均为不超过 100 的正整数。
Thoughts
草率地看完题目后,我联想到了NOIP2013货车运输。刚开始的想法(错的!)是找一棵最小生成树,然后进行一次树形DP,找到每个节点的儿子节点并统计个数cnt,则删去该节点之后最短路径长发生改变的就是cnt。举了一个例子,挂了,发现当产生了有多条路径到x点均为最小权值时会挂,于是又做了各种麻烦的操作改……然而,打了一发对拍,惨烈地挂了。谁说最短路径的路径一定在最小生成树上的……
期望得分:0 实际得分:(已和谐)
Solution
正解需要先跑一边SPFA跑出单源最短路,然后再以每个点检查,找出作为最短路径的边,并重新建图,然后对于每一个删去的节点,bfs一下,查看能访问到多少节点,未访问到的就是改变的节点数。
这叫做建最短路图。貌似还是很有用的。
吐槽:重新建图超级麻烦,还要写两个不同的链式前向星,数组开到手抖
Code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
template <typename Tp> inline void read(Tp &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
const int maxn=5010,maxm=20010;
int p,head[maxn],to[maxm<<1],w[maxm<<1],nxt[maxm<<1];
int n,m,dis[maxn],que[maxn],p2,h2[maxn],t2[maxm<<1],n2[maxm<<1];
bool inq[maxn],vis[maxn];
queue<int> q;
void insert(int u,int v,int tw)
{
to[++p]=v;
w[p]=tw;
nxt[p]=head[u];
head[u]=p;
}
void insert2(int u,int v)
{
t2[++p2]=v;
n2[p2]=h2[u];
h2[u]=p2;
}
void input()
{
int u,v,tw;
read(n),read(m);
for(int i=1;i<=m;i++)
{
read(u),read(v),read(tw);
insert(u,v,tw);insert(v,u,tw);
}
}
void spfa()
{
int now;
memset(dis,0x3f,sizeof(dis));
q.push(1);
dis[1]=0;inq[1]=true;
while(!q.empty())
{
now=q.front();
q.pop();
inq[now]=false;
for(int i=head[now];i;i=nxt[i])
if(dis[to[i]]>dis[now]+w[i])
{
int v=to[i];
dis[v]=dis[now]+w[i];
if(!inq[v])
inq[v]=true,q.push(v);
}
}
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
for(int j=head[i];j;j=nxt[j])
{
int v=to[j];
if(!vis[v]&&dis[v]==dis[i]+w[j])
vis[v]=true,insert2(i,v);
}
}
}
void work(int k)
{
int h=1,t=1,x,ans=0;
memset(vis,0,sizeof(vis));
vis[1]=1;que[1]=1;
while(h<=t)
{
x=que[h++];
for(int i=h2[x];i;i=n2[i])
if(!vis[t2[i]]&&t2[i]!=k)
vis[t2[i]]=true,que[++t]=t2[i];
}
for(int i=1;i<=n;i++)
if(!vis[i])
ans++;
printf("%d\n",ans);
}
int main()
{
freopen("problem.in","r",stdin);
freopen("problem.out","w",stdout);
input();
spfa();
printf("%d\n",n-1);
for(int i=2;i<=n;i++)
work(i);
return 0;
}