2023杭电多校第二场部分题解

剩下还有些题以后再补……

1001

据说是从Kayles游戏拓展来的,这个游戏在网上都没啥资料……大概是这样的:

有一列石子,每次可以取走一个或连续的两个,每次取走后剩下的石子在原位置不动,不能操作者败。

这个游戏的特点就是SG打表之后存在规律,就是从某个 n n n 开始往后SG值存在循环节。

如果要验证循环节存在的话,假设循环开始的前一位为 a a a,循环节长度为 b b b,那么检验前 2 a + 2 b 2a+2b 2a+2b 位即可,如果都满足规律那么在这之后一定是循环的。具体证明可以手玩一下。


所以这题是一样的,打表找规律即可,规律可以参考官方题解。

代码就不贴了。

1002

n = 1 n=1 n=1 时显然不存在什么讨论空间。

n > 1 n>1 n>1 时,假如有 0,那么一定是贪心尽可能操作左边连续的0。假如有多的步数,每两步可以相互抵消,假如还多了一步,那么就让之前的随便一步多延伸一位,最后一步再操作一次那一位。

假如只有 1,那么直接开始抵消即可。但有一种情况特殊,如果只能操作一次,那么就不能抵消了,只能让最后的 1 变成 0。

代码如下:

#include <bits/stdc++.h>
using namespace std;

char s[200010];

int main()
{
	int T;cin>>T;while(T--){
		int n;long long k;
		scanf("%d %lld",&n,&k);
		scanf("%s",s+1);
		if(n==1){
			if(k&1)puts("0");
			else puts("1");
			continue;
		}
		int zero=0;
		for(int i=1;i<=n;i++){
			if(s[i]=='0'){
				zero++;
				if(k){
					k--;s[i]='1';
					int j=i;
					while(j<n&&s[j+1]=='0')s[++j]='1';
					i=j;
				}
			}
		}
		if(zero==0&&k==1ll)s[n]='0';
		for(int i=1;i<=n;i++)
			putchar(s[i]);puts("");
	}
}

1004

考场上当然直接猜结论: 2 n − 1 − 1 2^{n-1}-1 2n11,然后就过了。

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define mod 998244353

int ksm(int x,int y){
	int re=1;
	for(;y;y>>=1){
		if(y&1)re=1ll*re*x%mod;
		x=1ll*x*x%mod;
	}
	return re;
}

int main()
{
	int T;cin>>T;while(T--){
		int n;cin>>n;
		cout<<(ksm(2,n-1)-1+mod)%mod<<'\n';
	}
}

证明的话,用数学归纳法。

f ( n ) f(n) f(n) n n n 个位置时的答案,假如现在是 n + 1 n+1 n+1 个位置,那么第一次转移时考虑:先只用前 n n n 个位置,将 f ( n ) f(n) f(n) 个物品转移出去,但是在第 f ( n ) f(n) f(n) 个物品出去时,可以再将第 f ( n ) + 1 f(n)+1 f(n)+1 个物品放到 n + 1 n+1 n+1 位置,然后再将第 f ( n ) f(n) f(n) 个物品放到 n + 1 n+1 n+1 位置,最后将剩下 f ( n ) − 1 f(n)-1 f(n)1 个物品都放到 n + 1 n+1 n+1 位置。于是这样转移出去了 f ( n ) + 1 f(n)+1 f(n)+1 个物品。
第二次转移则转移出去 f ( n − 1 ) + 1 f(n-1)+1 f(n1)+1 个物品,第三次 f ( n − 2 ) + 1 f(n-2)+1 f(n2)+1……最后一次 f ( 1 ) + 1 f(1)+1 f(1)+1

f ( 1 ) f(1) f(1) 本来没有定义,不妨设 f ( 1 ) = 0 f(1)=0 f(1)=0

于是 f ( n + 1 ) = ∑ i = 1 n f ( i ) + 1 = ∑ i = 1 n 2 n − 1 = 2 n − 1 f(n+1)=\sum_{i=1}^n f(i)+1=\sum_{i=1}^n 2^{n-1}=2^n-1 f(n+1)=i=1nf(i)+1=i=1n2n1=2n1 □ \square

1007

太抽象了,一开始想了半天要怎么做,最后实在没辙了bitset硬上然后就过了。

其实只需要看上下那两个单点有多少个同时相连的点,知道了这个之后组合数一下就能算出来chicken的身体部分的方案数。然后上面那两个鸡脚也是随便组合数一下就行。

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define mod 1000000007

#define cn getchar
template<class TY>void read(TY &x){
	x=0;int f1=1;char ch=cn();
	while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}
	while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn(); x*=f1;
}
template<class TY>void write2(TY x){
	if(x>9)write2(x/10);
	putchar(x%10+'0');
}
template<class TY>void write(TY x){
	if(x<0)putchar('-'),x=-x;
	write2(x);
}
bitset<1010> a[1010];
int du[1010];
long long C2(int x){
	return x*(x-1)/2;
}
long long C4(int x){
	return 1ll*x*(x-1)*(x-2)*(x-3)/24;
}

int main()
{
	int T;cin>>T;while(T--){
		int n,m;
		read(n);read(m);
		memset(du,0,sizeof(du));
		for(int i=1;i<=m;i++){
			int x,y;read(x);read(y);
			a[x][y]=1;a[y][x]=1;
			du[x]++;du[y]++;
		}
		long long ans=0;
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++){
				int c=(a[i]&a[j]).count();
				if(c>=4){
					ans+=C4(c)%mod*(C2(du[i]-4-a[i][j])+C2(du[j]-4-a[i][j]));
					ans%=mod;
				}
			}
		write(ans);puts("");
		for(int i=1;i<=n;i++)
			a[i].reset();
	}
}

1009

输出有几段字母即可。

1010

首先条件可以转化成:两个隔着一位相邻的朋友之间距离不能超过 m m m

f i , j f_{i,j} fi,j 表示最后一个选的朋友是 i i i,且距离上一个朋友至多为 j j j。那么枚举一个 k k k,用 f k , m − ( i − k ) f_{k,m-(i-k)} fk,m(ik) 更新 f i , i − k f_{i,i-k} fi,ik,然后再枚举 j j j,用 f i , j f_{i,j} fi,j 更新 f i , j + 1 f_{i,j+1} fi,j+1 即可。利用了一点儿类似前缀和的性质。

还需要一个滚动数组。代码如下:

#include <bits/stdc++.h>
using namespace std;

int n,m,a[20010];
int f[2010][2010];

int main()
{
	int Te;cin>>Te;while(Te--){
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			cin>>a[i];a[0]=a[n+1]=0;
		for(int i=0;i<m;i++)
			for(int j=0;j<m;j++)
				f[i][j]=(i==0?0:1e9);
		
		for(int i=1;i<=n+1;i++){
			for(int j=1;j<m;j++)f[i%m][j]=1e9;
			
			for(int j=max(0,i-m+1);j<i;j++)
				f[i%m][i-j]=a[i]+f[j%m][m-(i-j)];
			for(int j=1;j<m-1;j++)
				f[i%m][j+1]=min(f[i%m][j+1],f[i%m][j]);
		}
		cout<<f[(n+1)%m][m-1]<<'\n';
	}
}

1011

当时脑瘫去考虑情况数除以总情况数了,其实直接考虑概率更好算,所以推式子的时候要多考虑考虑不同方向。

对于一个确定的 k k k,假设 n n n 在第 i i i 位,那么取到 n n n 的前提是前 i − 1 i-1 i1 位中的最大值在前 k k k 位里,概率为 k i − 1 \dfrac k {i-1} i1k,而 n n n 在第 i i i 位的概率为 1 n \dfrac 1 n n1

所以有 ∑ i = k + 1 n k i − 1 × 1 n = k n ∑ i = k n − 1 1 i \sum_{i=k+1}^n\dfrac k {i-1}\times \dfrac 1 n=\dfrac k n\sum _{i=k}^{n-1} \dfrac 1 i i=k+1ni1k×n1=nki=kn1i1

预处理一个前缀和则每个 k k k 就都可以 O ( 1 ) O(1) O(1) 求解了。

或者对 k k k 求导可以发现最优解是 n e \dfrac n e en,直接检查 ⌊ n e ⌋ \lfloor \dfrac n e \rfloor en 以及周围两个即可。

代码如下:

#include <bits/stdc++.h>
using namespace std;

double sum[10010];
double calc(int n,int k){
	return 1.0*k/n*(sum[n-1]-sum[k-1]);
}

int main()
{
	for(int i=1;i<=10000;i++)
		sum[i]=sum[i-1]+1.0/i;
	int T;cin>>T;while(T--){
		int n;cin>>n;
		if(n<=2){
			cout<<"0\n";
			continue;
		}
		int ans=0;
		double val=calc(n,0);
		int k=n/exp(1);
		for(int i=max(k-1,1);i<=min(n,k+1);i++)
			if(calc(n,i)>val)ans=i,val=calc(n,i);
		cout<<ans<<'\n';
	}
}

1012

硬币的交换其实很像网络流中一个流的移动,很容易想到用网络流来整。

由于操作存在先后顺序,所以需要按时间拆点跑网络流,每个点还需要拆开中间建边限制每个时刻的硬币数。

很多点都是废的,不需要拆 n × m n\times m n×m 个点,把需要的点建出来就行了。

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define maxn 20010
#define inf 999999999

int n,m,k,lim[maxn],b[maxn];
int last[maxn],Id,S,T;
struct edge{int y,z,next;}e[maxn<<5];
int first[maxn],et;
void buildroad(int x,int y,int z){
	e[++et]=(edge){y,z,first[x]};
	first[x]=et;
}
void addedge(int x,int y,int z){
	buildroad(x,y,z);buildroad(y,x,0);
}
int h[maxn],q[maxn],st,ed,cur[maxn];
bool bfs(){
	memset(h,0,sizeof(h));
	q[st=ed=1]=S;h[S]=1;
	while(st<=ed){
		int x=q[st++];cur[x]=first[x];
		for(int i=first[x];i;i=e[i].next){
			int y=e[i].y;
			if(e[i].z&&!h[y]){
				h[y]=h[x]+1;
				q[++ed]=y;
			}
		}
	}
	return h[T]>0;
}
int dfs(int x,int flow){
	if(x==T)return flow;int re=0;
	for(int i=cur[x];i;i=e[i].next){
		int y=e[i].y;cur[x]=first[x];
		if(e[i].z&&h[y]==h[x]+1){
			int p=dfs(y,min(e[i].z,flow-re));re+=p;
			e[i].z-=p;e[i^1].z+=p;
			if(re==flow)break;
		}
	}
	if(!re)h[x]=0;
	return re;
}

int main()
{
	int Te;cin>>Te;while(Te--){
		cin>>n>>m>>k;
		for(int i=1;i<=Id;i++)first[i]=0;
		for(int i=1;i<=n;i++)last[i]=0;
		et=1;Id=2;S=1;T=2;
		
		for(int i=1;i<=n;i++)cin>>lim[i];
		for(int i=1;i<=n;i++)
			addedge(S,last[i]=++Id,1);
		for(int i=1;i<=m;i++){
			int x,y;cin>>x>>y;
			int X=++Id,Y=++Id,XX=++Id,YY=++Id;
			addedge(last[x],Y,1);
			addedge(last[y],X,1);
			addedge(X,XX,lim[x]);
			addedge(Y,YY,lim[y]);
			addedge(last[x],X,inf);
			addedge(last[y],Y,inf);
			last[x]=XX;last[y]=YY;
		}
		for(int i=1;i<=k;i++){
			int x;cin>>x;
			addedge(last[x],T,lim[x]);
		}
		
		int ans=0;
		while(bfs())ans+=dfs(S,inf);
		cout<<ans<<'\n';
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值