@(CSP模拟赛20190922)
两天的一二题都挺水的,期望本来可以得400,但是没有一天是得完了能得的分的。
DAY1
T1
优美的字符串(string)
【题目描述】
小 Y 送给小 F 一个字符串作为礼物,这个字符串只由’a’ 和’b’ 组成。
由于小 F 患有严重的强迫症,他觉得这个字符串并不优美,他决定对它做一些
操作:
每次操作从字符串中选择一个’ab’ 子串,并将其替换为’bba’。
如果一个字符串的所有’b’ 都在所有’a’ 前面,他认为这个字符串是优美的。
现在小 F 想知道,最少需要多少次操作,能使这个字符串是优美的,或者这个字
符串不可能变成优美的。
【输入格式】
从文件 string.in 中读入数据。
一行一个只由’a’ 和’b’ 组成的字符串。
【输出格式】
输出到文件 string.out 中。
输出一行一个整数,如果无解,输出“-1”。否则输出最少操作次数对.
1000000007
取. 模. 。
大水题,手推五分钟就切了。
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=1000007; const ll mod=1000000007; ll dis[N]; char c[N]; template<class T>inline void read(T &res){ static char ch;T flag=1; while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1; res=ch-48; while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-48; res*=flag; } int main() { freopen("string.in","r",stdin); freopen("string.out","w",stdout); scanf("%s",c); int n=strlen(c),tot=0; ll ans=0; c[n]='a'; for(int i=n-1,last=n;i>=0;--i) if(c[i]=='a'){ dis[++tot]=last-i-1; last=i; } for(int i=1;i<=tot;++i){ ans=(ans+dis[i])%mod; dis[i+1]=(dis[i]*2+dis[i+1])%mod; } printf("%lld\n",ans); return 0; }
T2
【题目描述】
小 X 同学有很强的计算能力,现在他正在玩一个游戏。
现在有一个正整数 x,每次操作他会将当前的数变为这个数写成二进制后 1 的
个数。
小 X 不断的进行操作,直到这个数变成 1 为止。
由于小 X 的计算能力很强,他现在给出一个 n,他想知道有多少不超过 n 的正整
数会在 k 次操作后变成 1。由于答案可能很大,请对 1000000007 取模。
【输入格式】
从文件 number.in 中读入数据。
第一行一个用二进制表示的正整数 n,含义如题目描述。
第二行一个整数 k, 含义如题目描述。
【输出格式】
输出到文件 number.out 中。
输出一个整数,表示答案对 1000000007 取模的值。
发现2^1000经过一次计算就降到了1000一下,所以可以1000一下暴力搜索,然后再用组合数求值。
记得特判下k=0和1的情况
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=10007; const ll mod=1000000007; int n,k,lim,num[N]; ll ans,fac[N]={1}; char ch[N]; ll fp(ll x,ll k){ ll ans=1,s=x; while(k){ if(k&1) ans=ans*s%mod; k>>=1; s=s*s%mod; } return ans; } inline ll inv(ll x){return fp(x,mod-2);} inline ll C(ll n,ll m){ if(n>m||n<0||m<0) return 0; return fac[m]*inv(fac[n])%mod*inv(fac[m-n])%mod; } ll cal(int x){ ll res=0,cnt=0; for(int i=0;i<n;++i) if(num[i]==1){ res=(res+C(x-cnt,n-i-1))%mod; ++cnt; } if(cnt==x) res=(res+1)%mod; return res; } void dfs(int onenum,int stp){ if(stp==k){ ans=(ans+cal(onenum))%mod; return; } if(onenum>lim) return; for(int i=max(fp(2,onenum)-1,2ll);i<=n;++i){ int cnt=0,temp=i; while(temp){ if(temp&1) ++cnt; temp>>=1; } if(cnt==onenum) dfs(i,stp+1); } } int main() { freopen("number.in","r",stdin); freopen("number.out","w",stdout); scanf("%s",ch); n=strlen(ch); scanf("%d",&k); if(k==0){ printf("1\n"); return 0; } for(int i=0;i<n;++i) num[i]=ch[i]-'0'; lim=log(n)/log(2)+1; for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod; dfs(1,1); if(k==1) --ans; printf("%lld\n",ans); return 0; }
T3
【题目描述】
S 市的有着非常特殊的城市规划,可以把它抽象成有 n 个点 n 条边的连通图。
S 市的市长对这样的城市规划十分不满意,他想删去其中的一条路使得任意两点间
有且仅有一条简单路径。
S 市的市民对这样的城市规划也十分不满意,具体地,市民的不满意度为任意两点
间最短距离的最大值。
现在小 X 想知道,删去满足条件的一条道路后,市民不满意度的最小值。
【输入格式】
从文件 city.in 中读入数据。
第一行一个正整数 n,表示城市的点数和边数。
接下来 n 行,每行三个正整数 ui
, vi
,wi,表示每条边的两个端点和长度。
【输出格式】
输出到文件 city.out 中。
输出一个整数,表示答案
考虑删掉一条边后,什么样的一条链会成为直径:
把这个图看成是很多棵树由一个环相连:
1.一棵树的直径就是这个图的直径。
2.两个树最深的两个点之间的路径。
删掉环上一条边显然不会影响第一种情况,考虑最小化第二种情况:
假设环的长度为k,环上的点为a1,a2,...,ak,令a0=ak。
令si表示ai到a1的距离。显然这是一个前缀和。
以这个点为根的树上最深的点深度为dep1,dep2,…,depk。
如果断开的路径为e(a{i-1},ai),分为三种情况:
1.从1-i-1选两个点能取到最大值。
2.从i-k选两个点取到最大值。
3.前后各选一个点取到最大值。
对于每条边,我们需要快速的求出三种情况的max。
对于第一种情况,两点x,y间的距离为depx+depy+(sy-sx)=(depx-sx)+(depy+sy)
对于第二种情况结果同第一种情况。
对于第三种情况:设x<y,距离为depx+depy+(sk-(sy-sx))=sk+(depx+sx)+(depy-sy)。
我们需要让三种情况的最大值最小。考虑求每种情况的最大值:
预处理depi-si,depi+si,维护前缀和后缀最大值,就能得到每种情况的最大值,记录所有最大值的最小值,就是答案。
反正我不会
DAY2
T1
【题目描述】
为缓解 S 城与日俱增的交通压力,S 城的市长准备修一条路。
S 城共有 n 个街区,它们由 m 条双向道路相连,每条道路的长度相等。
作为 S 城的天才,小 X 了解到 S 城的交通压力主要来自于最繁华的 S 街区和 T
街区。如果新修的路不能使 S 街区到 T 街区的距离缩短,就不能缓解 S 城的交通压力。
S 城的市长自然不了解这一点。现在小 X 想知道,有多少种修路方案不能缓解 S
城的交通压力。
注意,对于修路方案 (ui
, ti) 和 (ti
, ui) 视为同一种方案,新修的路不能在原图中存在。
【输入格式】
从文件 road.in 中读入数据。
第一行四个整数 n, m, S, T。表示 S 城的街区数,道路数,繁华的两个街区的编号。
接下来 m 行,每行两个数 ui
, vi 表示一条道路上的两个街区。
图中无重边和自环。
【输出格式】
输出到文件 road.out 中。
输出一行一个整数。表示不能缓解交通压力的方案数。
bfs求任意两点间的最短路,然后暴力枚举任意两点连边是否可以使最短路变短(类似dijsktra和floyed的松弛操作)
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=2007; int n,m,S,T,tot,ans,head[N],dis[N][N]; bool vis[N],edge[N][N]; struct Edge{ int to,next; }e[N<<1]; void add(int from,int to){ e[++tot].to=to; e[tot].next=head[from]; head[from]=tot; } template<class T>inline void read(T &res){ static char ch;T flag=1; while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1; res=ch-48; while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-48; res*=flag; } queue<int> q; void bfs(int sta){ memset(vis,false,sizeof(vis)); q.push(sta); vis[sta]=true; while(q.size()){ int u=q.front(); q.pop(); for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]) continue; vis[v]=true; dis[sta][v]=dis[sta][u]+1; q.push(v); } } } int main() { freopen("road.in","r",stdin); freopen("road.out","w",stdout); read(n);read(m);read(S);read(T); for(int i=1;i<=m;++i){ int u,v; read(u);read(v); add(u,v);add(v,u); edge[u][v]=edge[v][u]=true; } for(int i=1;i<=n;++i) bfs(i); for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j){ if(edge[i][j]) continue; if(dis[S][T]>dis[S][i]+dis[j][T]+1||dis[S][T]>dis[S][j]+dis[i][T]+1){ ++ans; // printf("%d %d\n",i,j); } } printf("%d\n",n*(n-1)/2-m-ans); return 0; }
T2
【题目描述】
everlasting 有 n 个神奇的集合,编号为 1n。开始时它们都是空的,现在 everlasting
要对它们进行两种操作:
- 将元素 x 加入编号 [l,r] 的集合中。神奇的是,如果一个集合原本就有 x,那么
该集合中所有元素的个数都会翻倍 - 询问编号 [l,r] 的集合元素个数的和,对 998244353 取模。
everlasting 当然不会做啦,但是他想考考你...
【输入格式】
从文件 multiset.in 中读入数据。
第一行两个正整数 n, q, 表示集合个数和询问数量。
接下来 q 行,首先是一个整数 opt:
若 opt = 1,接下来三个整数 l,r, x,表示向编号 [l,r] 的集合中加入 x。
若 opt = 2,接下来两个整数 l,r,表示询问编号 [l,r] 的集合的元素个数和。
【输出格式】
输出到文件 multiset.out 中。
对于每个询问,输出一行一个整数,表示答案。
显然可以开n个线段树来维护n个元素的覆盖情况,动态开点防止空间爆炸。剩下就很简单了。
考场调了2.5h也没打出来,旁边的myg大佬一下就打完了,再此膜拜。
#include<cstdio> using namespace std; #define ll long long const ll mod=998244353; const int N=8000007; int n,q,ndnum,root,a[N>>5],ls[N],rs[N]; ll sum[N],fla[N],flm[N]; template<class T>inline void read(T &res){ static char ch;T flag=1; while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1; res=ch-48; while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-48; res*=flag; } int build(int l,int r){ int p=++ndnum; if(l==r){ sum[p]=0; flm[p]=1; return p; } int mid=(l+r)>>1; ls[p]=build(l,mid); rs[p]=build(mid+1,r); sum[p]=sum[ls[p]]+sum[rs[p]]; flm[p]=1; return p; } void pushdown(int p,int l,int r){ int mid=(l+r)>>1; sum[ls[p]]=(sum[ls[p]]*flm[p]%mod+(mid-l+1)*fla[p]%mod)%mod; sum[rs[p]]=(sum[rs[p]]*flm[p]%mod+(r-mid)*fla[p]%mod)%mod; fla[ls[p]]=(fla[ls[p]]*flm[p]%mod+fla[p])%mod; fla[rs[p]]=(fla[rs[p]]*flm[p]%mod+fla[p])%mod; flm[ls[p]]=flm[ls[p]]*flm[p]%mod; flm[rs[p]]=flm[rs[p]]*flm[p]%mod; fla[p]=0;flm[p]=1; } int xl,xr,yl,yr,x; ll query(int l,int r,int p){ if(xl<=l&&r<=xr) return sum[p]; pushdown(p,l,r); int mid=(l+r)>>1; ll res=0; if(xl<=mid) res+=query(l,mid,ls[p]); if(xr>mid) res+=query(mid+1,r,rs[p]); return res%mod; } int sett(int l,int r,int p){ if(p==0) p=++ndnum; if(yl<=l&&r<=yr){ sum[p]=1; fla[p]=true; return p; } int mid=(l+r)>>1; if(yl<=mid){ if(!ls[p]) ls[p]=++ndnum; ls[p]=sett(l,mid,ls[p]); } if(yr>mid){ if(!rs[p]) rs[p]=++ndnum; rs[p]=sett(mid+1,r,rs[p]); } if(sum[ls[p]]==sum[rs[p]]) sum[p]=sum[ls[p]]; else sum[p]=-1; return p; } int _get(int l,int r,int p){ if(yl<=l&&r<=yr) return sum[p]; int mid=(l+r)>>1; int la=100,ra=100; if(yl<=mid){ if(!ls[p]) ls[p]=++ndnum; if(fla[p]==1) sum[ls[p]]=fla[ls[p]]=1; la=_get(l,mid,ls[p]); } if(yr>mid){ if(!rs[p]) rs[p]=++ndnum; if(fla[p]==1) sum[rs[p]]=fla[rs[p]]=1; ra=_get(mid+1,r,rs[p]); } if(la==100) return ra; if(ra==100) return la; if(la==ra) return la; return -1; } void modify(int l,int r,int p,int c){ if(xl<=l&&r<=xr){ if(c==1){ sum[p]=(sum[p]+r-l+1)%mod; fla[p]=(fla[p]+1)%mod; return; } if(c==2){ sum[p]=sum[p]*2%mod; fla[p]=fla[p]*2%mod; flm[p]=flm[p]*2%mod; return; } yl=l,yr=r; int y=_get(1,n,x); if(y==0) c=1; else if(y==1) c=2; else c=0; if(c==1){ sum[p]=(sum[p]+r-l+1)%mod; fla[p]=(fla[p]+1)%mod; return; } if(c==2){ sum[p]=sum[p]*2%mod; fla[p]=fla[p]*2%mod; flm[p]=flm[p]*2%mod; return; } } pushdown(p,l,r); int mid=(l+r)>>1; if(xl<=mid) modify(l,mid,ls[p],c); if(xr>mid) modify(mid+1,r,rs[p],c); sum[p]=(sum[ls[p]]+sum[rs[p]])%mod; } int main() { freopen("multiset.in","r",stdin); freopen("multiset.out","w",stdout); read(n);read(q); ndnum=n; root=build(1,n); while(q--){ int opt; read(opt); if(opt==1){ read(xl);read(xr);read(x); modify(1,n,root,0); yl=xl;yr=xr; sett(1,n,x); } else if(opt==2){ read(xl);read(xr); printf("%lld\n",query(1,n,root)); } } // while(q--){ // int opt; // read(opt); // if(opt==1){ // read(yl);read(yr);read(x); // sett(1,n,x); // } // else if(opt==2){ // read(yl);read(yr);read(x); // printf("%d\n",_get(1,n,x)); // } // } return 0; }
T3
【题目描述】
小 X 同学觉得树上问题太毒瘤了,于是决定将树上的边删去,最终变成一个点。
现在有一个 n 个节点的树,他的游戏是这样的:
- 从剩下的所有边中等概率随机选中一条边 T。
- 将这条边删去,若这条边相连的两个点编号为 u 和 v,新建一个点 x,这个点与
所有与 u 和 v 相邻的点有边,最后删去 u 和 v 及与它们相连的边,x 的编号等概率随
机命名为 u 或 v。
不断重复上述步骤,知道只剩下一个点。
现在小 X 想知道,对于每个编号,最后剩下该编号的点的概率。
树是一个没有环的连通图。
【输入格式】
从文件 tree.in 中读入数据。
第一行一个整数 n。
接下来 n 1 行,第 i 行两个整数 ui
, vi,表示第 i 条边连接的两个点。