YZhe的头像

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
要对它们进行两种操作:

  1. 将元素 x 加入编号 [l,r] 的集合中。神奇的是,如果一个集合原本就有 x,那么
    该集合中所有元素的个数都会翻倍
  2. 询问编号 [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 个节点的树,他的游戏是这样的:

  1. 从剩下的所有边中等概率随机选中一条边 T。
  2. 将这条边删去,若这条边相连的两个点编号为 u 和 v,新建一个点 x,这个点与
    所有与 u 和 v 相邻的点有边,最后删去 u 和 v 及与它们相连的边,x 的编号等概率随
    机命名为 u 或 v。
    不断重复上述步骤,知道只剩下一个点。
    现在小 X 想知道,对于每个编号,最后剩下该编号的点的概率。
    树是一个没有环的连通图。
    【输入格式】
    从文件 tree.in 中读入数据。
    第一行一个整数 n。
    接下来 n 1 行,第 i 行两个整数 ui
    , vi,表示第 i 条边连接的两个点。
posted @ 2019-10-12 08:11  YZhe  阅读(256)  评论(0编辑  收藏  举报
ヾ(≧O≦)〃嗷~