2016中国大学生程序设计竞赛(长春)-重现赛 题解
E.The Fastest runner ms.zhang
给一个图,n点n边,问走过所有点的最优方案(总路程最小;其次,起点的序号最小;再次,终点的序号最小)
网上有代码的其实。。。只是看不惯那冗长的代码和较慢的运行速度,就决定自己写一个
整棵树只有n-1边,这种图的话就是一个环连着几棵子树
s和t之间肯定有1条路只走1次,其他路走2次
最短路程有2种情况:
s和t最短路不过环,则st最短路全程在树上,环只走1次,设dis(s,t)是s和t间最短路,looplength是环长度,则答案是2*n-looplength-dis(s,t)
这部分树dp就能解决,不过要注意最大次大值的取值问题(最后的WA就是因为这个)和序号的最小化
s和t最短路过环,则st通路有环部分,由于这种情况环部分的路径不可能覆盖全环,没有被通路覆盖的部分路必须走2次
由于只要覆盖全点,走2次那部分有1条边不用走,少走2次
通路可以选择上面或下面,这样就要选择最长覆盖以减少走2次的路
求这个要用扫描线o(n)的方法防超时
求出区间前缀(不横跨环上0点)和后缀(横跨环上0点)里面元素的最大值与扫描线的距离
距离是dep[i]+i+dep[j]-j,就是说随着扫描线和区间内最大值的变化,要加减的值是按它们序号来的
同样要注意序号最小化
被坑了2天,真不该(坑点:序号最小化)
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<queue> #include<stack> #include<math.h> #include<vector> #include<map> #include<set> #include<stdlib.h> #include<cmath> #include<string> #include<algorithm> #include<iostream> using namespace std; typedef __int64 ll; const int N=200025; int max(int a,int b){return a>b?a:b;} int min(int a,int b){return a<b?a:b;} int cnt; int head[N],inu[N],circ[N]; int dep[N][2],pto[N][2],cto[N][2]; int que[N],lop[N]; int to[N<<1],nxt[N<<1]; void addedge(int u,int v) { to[cnt]=v;nxt[cnt]=head[u];head[u]=cnt++; to[cnt]=u;nxt[cnt]=head[v];head[v]=cnt++; } //相同路径数的索引号选择 void isvalid(int& a,int& b,int c,int d) { int cmpr=min(a,b)-min(c,d); if(cmpr>0 || (cmpr==0 && max(a,b)>max(c,d))) a=c,b=d; } void bfs(int n) { memset(dep,0,sizeof(dep)); memset(circ,0,sizeof(circ)); int tai=0,hed=0,i,u,v; for(i=0;i<n;i++) { if(inu[i]==1)que[tai++]=i; pto[i][0]=pto[i][1]=cto[i][0]=cto[i][1]=i;//深度为0,则端点就是本身 } while(tai!=hed) { u=que[hed++];inu[u]--; if(circ[u]==dep[u][0]+dep[u][1]) { isvalid(cto[u][0],cto[u][1],pto[u][0],pto[u][1]); } else if(circ[u]<dep[u][0]+dep[u][1]) { circ[u]=dep[u][0]+dep[u][1]; cto[u][0]=pto[u][0]; cto[u][1]=pto[u][1]; } for(i=head[u];i!=-1;i=nxt[i]) { v=to[i]; if(inu[v]<=0)continue; if(dep[v][0]<dep[u][0]+1) { if(dep[v][1]<dep[v][0]) { dep[v][1]=dep[v][0]; pto[v][1]=pto[v][0]; } else if(dep[v][1]==dep[v][0] && pto[v][1]>pto[v][0]) { pto[v][1]=pto[v][0]; } dep[v][0]=dep[u][0]+1; pto[v][0]=pto[u][0]; } else if(dep[v][0]==dep[u][0]+1 && pto[v][0]>pto[u][0]) { pto[v][0]=pto[u][0]; } else if(dep[v][1]<dep[u][0]+1) { dep[v][1]=dep[u][0]+1; pto[v][1]=pto[u][0]; } else if(dep[v][1]==dep[u][0]+1 && pto[v][1]>pto[u][0]) { pto[v][1]=pto[u][0]; } if(circ[v]==circ[u]) { isvalid(cto[v][0],cto[v][1],cto[u][0],cto[u][1]); } else if(circ[v]<circ[u]) { circ[v]=circ[u]; cto[v][0]=cto[u][0]; cto[v][1]=cto[u][1]; } if(--inu[v]==1) { que[tai++]=v; } } } } int main() { int t,n,u,v,ans,bestu,bestt,i; scanf("%d",&t); for(int h=1;h<=t;h++) { ans=99999999;bestu=99999999;bestt=99999999; memset(head,-1,sizeof(head));cnt=0; memset(inu,0,sizeof(inu)); scanf("%d",&n); for(i=0;i<n;i++) { scanf("%d%d",&u,&v);u--,v--; addedge(u,v),inu[u]++,inu[v]++; } bfs(n); int lpl=0,max1=-99999999,pmax1=99999999,max2=-99999999,pmax2=99999999; for(i=0;inu[i]==0;i++); for(u=i;inu[u]>=2;u=to[v]) { lop[lpl++]=u; for(v=head[u];v!=-1 && inu[to[v]]<2;v=nxt[v]); if(v==-1)break; inu[u]--; } lop[lpl]=lop[0]; for(i=0;i<lpl;i++) { if(circ[lop[i]]==dep[lop[i]][0]+dep[lop[i]][1]) { isvalid(cto[lop[i]][0],cto[lop[i]][1],pto[lop[i]][0],pto[lop[i]][1]); } else if(circ[lop[i]]<dep[lop[i]][0]+dep[lop[i]][1]) { circ[lop[i]]=dep[lop[i]][0]+dep[lop[i]][1]; cto[lop[i]][0]=pto[lop[i]][0]; cto[lop[i]][1]=pto[lop[i]][1]; } } for(i=0;i<lpl;i++) { int tmp=2*n-lpl-circ[lop[i]];//s and t are put in the same trees if(ans==tmp) { isvalid(bestu,bestt,cto[lop[i]][0],cto[lop[i]][1]); } else if(ans>tmp) { ans=tmp; bestu=cto[lop[i]][0]; bestt=cto[lop[i]][1]; } //notice:The loop can walk without 1 edge if(pmax1!=99999999) { tmp=2*n-(i+dep[lop[i]][0]+max1)-2; if(ans==tmp) { isvalid(bestu,bestt,pto[lop[i]][0],pto[lop[pmax1]][0]); } else if(ans>tmp) { ans=tmp; bestu=pto[lop[i]][0]; bestt=pto[lop[pmax1]][0]; } } if(max1<dep[lop[i]][0]-i) { max1=dep[lop[i]][0]-i; pmax1=i; } else if(max1==dep[lop[i]][0]-i && pto[lop[pmax1]][0]>pto[lop[i]][0]) { pmax1=i; } if(pmax2!=99999999) { tmp=2*n-(lpl-i+dep[lop[lpl-i-1]][0]+max2)-2; if(ans==tmp) { isvalid(bestu,bestt,pto[lop[lpl-i-1]][0],pto[lop[pmax2]][0]); } else if(ans>tmp) { ans=tmp; bestu=pto[lop[lpl-i-1]][0]; bestt=pto[lop[pmax2]][0]; } } if(max2<dep[lop[lpl-i-1]][0]+i) { max2=dep[lop[lpl-i-1]][0]+i; pmax2=lpl-i-1; } else if(max2==dep[lop[lpl-i-1]][0]+i && pto[lop[pmax2]][0]>pto[lop[lpl-i-1]][0]) { pmax2=lpl-i-1; } } printf("Case #%d: %d %d %d\n",h,ans,min(bestu,bestt)+1,max(bestu,bestt)+1); } return 0; }
G.Instability
给一个图,问最少3个元素,含独立集或团的集合有多少个
其实很简单的,6个点的话,不是含团就是含独立集
枚举3~5个点的集合,时间复杂度最多c(50,3)+c(50,4)+c(50,5)<c(50,5)*3=2118760*3
然后就AC了
I. Sequence II
题意:不修改数列的数,在线查询区间中位数
题解:这种没有修改的当然是主席树大法好,为了这个还专门学了一点时间
这个链接http://www.cnblogs.com/zyf0163/p/4749042.html最好懂
就是把第k数据转换为arc(a(k)),然后作为元素插入树k~树n的第arc(a(k))个结点
由于存在大量同构树,可以直接连接而不是新建树结点,这样就节省了memory&&time
至于中位数的查询,先查询[l,r]下有多少不同的数->u,再从l开始查第ceil(u/2)大数
/* *********************************************** ┆ ┏┓ ┏┓ ┆ ┆┏┛┻━━━┛┻┓ ┆ ┆┃ ┃ ┆ ┆┃ ━ ┃ ┆ ┆┃ ┳┛ ┗┳ ┃ ┆ ┆┃ ┃ ┆ ┆┃ ┻ ┃ ┆ ┆┗━┓ 马 ┏━┛ ┆ ┆ ┃ 勒 ┃ ┆ ┆ ┃ 戈 ┗━━━┓ ┆ ┆ ┃ 壁 ┣┓┆ ┆ ┃ 的草泥马 ┏┛┆ ┆ ┗┓┓┏━┳┓┏┛ ┆ ┆ ┃┫┫ ┃┫┫ ┆ ┆ ┗┻┛ ┗┻┛ ┆ ************************************************ */ //#pragma comment(linker, "/STACK:102400000,102400000") #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <stack> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <bitset> using namespace std; #define rep(i,a,b) for (int i=(a),_ed=(b);i<=_ed;i++) #define per(i,a,b) for (int i=(b),_ed=(a);i>=_ed;i--) #define pb push_back #define mp make_pair const int inf_int = 2e9; const long long inf_ll = 2e18; #define inf_add 0x3f3f3f3f #define mod 1000000007 #define LL long long #define ULL unsigned long long #define MS0(X) memset((X), 0, sizeof((X))) #define SelfType int SelfType Gcd(SelfType p,SelfType q){return q==0?p:Gcd(q,p%q);} SelfType Pow(SelfType p,SelfType q){SelfType ans=1;while(q){if(q&1)ans=ans*p;p=p*p;q>>=1;}return ans;} #define Sd(X) int (X); scanf("%d", &X) #define Sdd(X, Y) int X, Y; scanf("%d%d", &X, &Y) #define Sddd(X, Y, Z) int X, Y, Z; scanf("%d%d%d", &X, &Y, &Z) #define reunique(v) v.resize(std::unique(v.begin(), v.end()) - v.begin()) #define all(a) a.begin(), a.end() #define mem(x,v) memset(x,v,sizeof(x)) typedef pair<int, int> pii; typedef pair<long long, long long> pll; typedef vector<int> vi; typedef vector<long long> vll; inline int read(){int ra,fh;char rx;rx=getchar(),ra=0,fh=1;while((rx<'0'||rx>'9')&&rx!='-')rx=getchar();if(rx=='-')fh=-1,rx=getchar();while(rx>='0'&&rx<='9')ra*=10,ra+=rx-48,rx=getchar();return ra*fh;} const int N = 2e5 + 5; struct node { int l,r,sum; }tree[N*40]; int tot,cnt; void update(int l,int r,int &x,int y,int k,int val) { int tmp = x; x = ++tot; tree[x] = tmp ? tree[tmp] : tree[y]; tree[x].sum += val; if(l==r) return; int mid = (l+r) >> 1; if(k<=mid) update(l,mid,tree[x].l,tree[y].l,k,val); else update(mid+1,r,tree[x].r,tree[y].r,k,val); } int query(int l,int r,int x,int k) { if(l==r) return l; int mid = (l+r) >> 1; int sum = tree[tree[x].l].sum; if(k<=sum) return query(l,mid,tree[x].l,k); else return query(mid+1,r,tree[x].r,k-sum); } int getsum(int l,int r,int x,int L,int R) { if(L<=l && r<=R) return tree[x].sum; int mid = (l+r) >> 1; int res = 0; if(L<=mid) res += getsum(l,mid,tree[x].l,L,R); if(R>mid) res += getsum(mid+1,r,tree[x].r,L,R); return res; } int a[N],pos[N],rt[N]; int ans[N]; void init() { tot = 0; MS0(pos); MS0(rt); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); //ios::sync_with_stdio(0); //cin.tie(0); int cas = 1; int t = read(); while(t--) { int n = read(), m = read(); init(); for(int i=1;i<=n;i++) a[i] = read(); for(int i=n;i;i--)//倒着插入,以便从l开始计数 { if(pos[a[i]]) { update(1,n,rt[i],rt[i+1],pos[a[i]],-1);//去除重复的数 } update(1,n,rt[i],rt[i+1],i,1);//前缀树继承上一次连接的 pos[a[i]] = i; } for(int i=1;i<=m;i++) { int l = read(), r = read(); l = (l + ans[i-1]) % n + 1; r = (r + ans[i-1]) % n + 1; if(l>r) swap(l,r); int sum = (getsum(1,n,rt[l],l,r) + 1) / 2;//向上取整,题目要求 ans[i] = query(1,n,rt[l],sum); } printf("Case #%d:",cas++); for(int i=1;i<=m;i++) printf(" %d",ans[i]); printf("\n"); } return 0; }
J. Ugly problem
题意:把大数字拆成不超过50个的回文数
题解:每次用不超过大数字的回文数去减,得到的结果要再这样执行,注意"10"这个数的坑点
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <string> #include <cstdlib> #include <vector> #include <set> #include <map> using namespace std; const double eps=1e-8; int len; //大数乘法 void subtract(char *a,char *b,char *c) { for(int i=len-1;i>=0;i--) { c[i]=a[i]-b[i]+'0'; if(c[i]<'0')c[i]+=10,a[i-1]--; } } char str[2][1005],sub[50][1005]; int main() { int i,j,k,n; scanf("%d",&n);getchar(); for(int h=1;h<=n;h++) { memset(sub,'0',sizeof(sub)); gets(str[0]); len=strlen(str[0]); int old=1,now=0; int cnt=0; for(i=0;str[now][i];)//前置0到末尾,整个数就是0 { old^=1,now^=1;//滚动数组 if(!strcmp(str[old]+i,"10"))//10=9+1 { sub[cnt][0]='9';sub[cnt][1]=0;cnt++; sub[cnt][0]='1';sub[cnt][1]=0;cnt++; break; } for(j=i;j<=len-1-j+i && str[old][j]<=str[old][len-1-j+i];j++); strcpy(sub[cnt],str[old]); if(j<len-1-j+i)//对折后有小于的,就只能取较小的回文数 { sub[cnt][(i+len-1)/2]--; for(j=(i+len-1)/2;j>=0 && sub[cnt][j]<'0';j--)sub[cnt][j]+=10,sub[cnt][j-1]--; } for(j=0;sub[cnt][j]=='0';j++);//去前导0 for(k=j;k<=len-1-k+j;k++)sub[cnt][len-1-k+j]=sub[cnt][k];//根据前半串对折构造后半串 sub[cnt][len]=0; subtract(str[old],sub[cnt],str[now]); for(;str[now][i]=='0';i++); cnt++; } printf("Case #%d:\n%d\n",h,cnt); for(i=0;i<cnt;i++) { char *s; for(s=sub[i];*s=='0';s++); puts(s); } } return 0; }
K.Binary Indexed Tree
题意:分别对树状数组执行add(r,1)和add(l-1,-1)之后,发生改变的结点数量是s(l,r)
求[1,n]里所有区间的s(l,r)和
题解:发现相同右区间的那些s(l,r)的总和有规律
如12,在右区间是12~15时贡献是12,到了>=16的右区间,贡献变成4
可以发现贡献分量与二进制有关这不废话吗!为什么非要暴力计算不可呢
可以理解为异或,把不同二进制数1的个数统计起来,然后答案其实很明显:
\sum_{r=1}^{n}\sum_{l=0}^{r-1}(cnt_l+cnt_r-2cnt_{lcp(l,r)})=\frac{1}{2}\sum_{l=0}^{n}\sum_{r=0}^{n}(cnt_l+cnt_r-2cnt_{lcp(l,r)})r=1∑nl=0∑r−1(cntl+cntr−2cntlcp(l,r))=21l=0∑nr=0∑n(cntl+cntr−2cntlcp(l,r))
其中cnt(l)是l作为二进制时1的个数 lcp(l,r)指二进制时l和r的最长公共前缀
减2次lcp(l,r)是异或,减1次的那种是位或运算
为什么直接就是1/2?l和r相等的情况直接被减成0了,就不用考虑容斥
转换成这个,什么都好算
前面2个就是照规律计数,后面打表出来发现规律也可以算。。。不就是把前面的一维计数对应部位平方吗
这个方法最简单了,不像其他方法需要复杂的计算步骤
//Thanks to Fsss_7 #include<map> #include<set> #include<cmath> #include<queue> #include<bitset> #include<math.h> #include<vector> #include<string> #include<stdio.h> #include<cstring> #include<iostream> #include<algorithm> #pragma comment(linker, "/STACK:102400000,102400000") using namespace std; const int N=400010; const int M=50010; const int mod=1000000007; const int MOD1=1000000007; const int MOD2=1000000009; const double EPS=0.00000001; typedef long long ll; const ll MOD=1000000007; const int INF=1000000010; const ll MAX=1ll<<55; const double eps=1e-5; const double inf=~0u>>1; const double pi=acos(-1.0); typedef long double db; typedef unsigned int uint; typedef unsigned long long ull; int a[100]; ll b[100],c[100],e[100]; ll get(int len) { ll ret=0; for (int i=0;i<len;i++) ret+=a[i]; for (int i=len-1;i>=0;i--) if (a[i]) { if (i) (ret+=c[i-1])%MOD; (ret+=b[i+1]*e[i]%MOD)%=MOD; } else (ret+=b[i+1]*e[i]%MOD)%=MOD; return ret; } ll getlcp(int len) { ll ret=0; for (int i=len-1;i>=0;i--) if (a[i]) { if (i) (ret+=(c[i-1]+1)*(c[i-1]+1)%MOD)%=MOD; else (ret+=1ll)%=MOD; (ret+=b[i+1]*e[i]%MOD*e[i]%MOD)%=MOD; } else (ret+=b[i+1]*e[i]%MOD*e[i]%MOD)%=MOD; return ret; } int main() { int i,ca,T,len; ll n,m,ans; scanf("%d", &T); for (i=0;i<63;i++) e[i]=(1ll<<i)%MOD; for (ca=1;ca<=T;ca++) { scanf("%I64d", &n);m=n; for (len=0;m;m>>=1) a[len++]=m%2; b[len]=0;c[0]=a[0]; for (i=1;i<len;i++) c[i]=(c[i-1]+(a[i]*1ll<<i))%MOD; for (i=len-1;i>=0;i--) b[i]=((b[i+1]<<1)+a[i])%MOD; ans=((n+1)%MOD*get(len))%MOD; printf("Case #%d: %I64d\n", ca, ((ans-getlcp(len))%MOD+MOD)%MOD); } return 0; }