T1 nim
第一道题就是博弈论,考试的时候愣是没有找到规律,其实是SG函数最简单的应用,如果A[i]为奇数,SG(A[i])=A[i]+1,否则SG(A[i])=A[i]-1。最后将所有SG函数异或即可,博弈论的基本结论:所有情况异或和为0,则先手必败,反之先手必胜,代码极短:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; int T,n; long long sum; int a[maxn]; int main(){ scanf("%d",&T); while(T--){ sum=0; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]%2==0) a[i]--; else a[i]++; sum^=a[i]; } if(sum==0) printf("B\n"); else printf("A\n"); } return 0; }
T2 party
一道图论题,思维和方法都比较简单,算是今天最简单的一道题。但考试的时候忘了tarjan怎么打,还是爆零了。首先将所有点的度数统计一遍,如果度数小于题目中规定的d值,就不能要,所以要把这些点删去。现将原图中度数小于d的点存入队列之中,然后用类似拓扑排序的方法删点,打上删除标记,并且将出边所对应的点的度数减一,在删点的过程中,有可能又会遇到新的点,他的度数小于d,所以当遇到这些点的时候还是要将他们放入队列中,继续搜索直到原图满足我们的要求。接下来就用tarjan遍历图上所有的连通块,找最大解。但注意在回溯的时候还是要保存每一个连通块的最小值,如果大小相同,那么就要优先选择最小值较小的那个连通块。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; const int inf=0x7fffffff; int n,m,d,x,y; int deg[maxn]; struct node{ int nxt,to; }edge[maxn*3]; int head[maxn],cnt; void add(int x,int y){ edge[++cnt].nxt=head[x]; edge[cnt].to=y; head[x]=cnt; } bool del[maxn]; queue<int> q; void topo(){//枚举删边 while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i;i=edge[i].nxt){ int v=edge[i].to; deg[v]--; if(deg[v]<d&&!del[v]) q.push(v),del[v]=true; //有新的不满足要求的点还是要删去 } } } stack<int> sta; bool vis[maxn],ins[maxn]; int dfn[maxn],low[maxn],in; int tot,ljb;//tot连通块的编号 int size[maxn];//每个联通块的大小 int ans[maxn];//那些元素属于这个连通块 int mi[maxn]; //连通块的最小值 int minn; int maxx,maxtot; int tong[maxn]; void paint(){ int v=sta.top(); sta.pop(); size[tot]++; ins[v]=false; ans[v]=tot; if(v<mi[tot]) mi[tot]=v; } void tarjan(int x){//tarjan遍历连通块 dfn[x]=low[x]=++in; vis[x]=true; sta.push(x); ins[x]=true; for(int i=head[x];i;i=edge[i].nxt){ int v=edge[i].to; if(del[v]) continue; if(!vis[v]){ tarjan(v); low[v]=min(low[x],dfn[v]); } else if(ins[v]){ low[x]=min(dfn[v],low[x]); } } if(dfn[x]==low[x]){//找到了一个 tot++; while(sta.top()!=x){ paint(); } paint(); } } int main(){ scanf("%d%d%d",&n,&m,&d); for(register int i=1;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y);add(y,x); deg[x]++;deg[y]++; } for(int i=1;i<=n;i++){ if(deg[i]<d) q.push(i),del[i]=true; } topo(); for(int i=1;i<=n;i++){ if(!dfn[i]&&!del[i]) tarjan(i); } for(int i=1;i<=tot;i++){ if(size[i]>size[maxtot]) minn=mi[i],maxtot=i; else if(size[i]==size[maxtot]&&minn>mi[i]) minn=mi[i],maxtot=i;//还需要注意特判 } printf("%d\n",size[maxtot]); for(int i=1;i<=n;i++){ if(ans[i]==maxtot) tong[++ljb]=i; } sort(tong+1,tong+1+ljb); for(int i=1;i<=ljb;i++) printf("%d ",tong[i]); printf("\n"); return 0; }
T3 history
其实想到思路还是比较简单的,这道题方法还是比较多的,但是我还是只能稍微理解在线的按秩合并的并查集做法。而且这道题的取模也是神仙,不能按他题上的搞。
并查集不能用路径压缩,他的父亲的时间戳一定是比他晚的,因为连边时的时间戳是保存在秩较小的点上的。最后就按照题上的判断就完了。
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7;; int n,m; char opt[666]; int fa[maxn]; int size[maxn]; int year[maxn]; int cnt; bool angry; int x,y,c,t; int get(int x,int tt){ while(x!=fa[x]&&year[x]<=tt) x=fa[x]; return x; } int main(){ scanf("%d%d",&n,&m); for(int i=0;i<n;i++) fa[i]=i,size[i]=1; for(int i=1;i<=m;i++){ scanf("%s",opt); if(opt[0]=='K') scanf("%d",&c),angry=false; if(opt[0]=='R'){ scanf("%d%d",&x,&y); if(angry){ if((x+=c)>=n) x-=n; if((y+=c)>=n) y-=n; } cnt++; int f1=get(x,cnt); int f2=get(y,cnt); if(f1==f2) continue; if(size[f1]<size[f2]){ fa[f1]=f2; size[f2]+=size[f1]; year[f1]=cnt;//father的时间戳更晚,路径的时间戳保存在秩较小的节点上 } else{ fa[f2]=f1; size[f1]+=size[f2]; year[f2]=cnt; } } if(opt[0]=='T'){ scanf("%d%d%d",&x,&y,&t); angry=((get(x,cnt-t)==get(y,cnt-t))||(get(x,cnt)!=get(y,cnt)));//不符合题中要求 if(angry) printf("N\n"); else printf("Y\n"); } } return 0; }