good problems-9

1. D2. Xor-Subsequence (hard version)
题解
easy版本

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e6+101;
const int MOD=998244353;
const ll inf=2147383647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int dp[maxn],a[maxn];
bool check(int p,int p1){
	if((a[p]^p1)<(a[p1]^p))return 1;
	return 0;
}
void solve(){
	int n=read();
	for(int i=0;i<n;i++){
		a[i]=read();dp[i]=0;
	}
	int ans=0,maxx=0;
	for(int i=0;i<n;i++){
		int pos=(i>>8)<<8;
		dp[i]=1;
		for(int j=pos;j<i;j++){
			if(check(j,i))dp[i]=max(dp[i],dp[j]+1);
		}
		ans=max(ans,dp[i]);
	}
	cout<<ans<<endl;
	return ;
}

int main(){
	int t=read();
	while(t--)solve(); 
    return 0;
}

hard

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=6e6+101;
const int MOD=998244353;
const ll inf=2147383647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int cnt,tr[maxn][2],f[maxn][2],dp[maxn],a[maxn],n;
void insert(int x,int id){
	int u=0;
	for(int i=30;i>=0;i--){
		int bit=(x>>i)&1;
		if(!tr[u][bit])tr[u][bit]=++cnt;
		u=tr[u][bit];
		f[u][(id>>i)&1]=max(f[u][(id>>i)&1],dp[id]);
	}
	return ;
}
int query(int x,int k){
	int u=0,ans=1;
	for(int i=30;i>=0;i--){
		int bit=(x>>i)&1,rev=tr[u][bit^1];
		ans=max(ans,f[rev][((k>>i)&1)^1]+1);
		u=tr[u][bit];
		if(!u)break;
	}
	return ans;
}
void solve(){
	n=read();for(int i=0;i<n;i++)a[i]=read(),dp[i]=0;
	for(int i=0;i<=cnt;i++)tr[i][0]=tr[i][1]=f[i][0]=f[i][1]=0;
	cnt=0;int ans=0;
	for(int i=0;i<n;i++){
		dp[i]=query(a[i]^i,a[i]);
		insert(a[i]^i,i);
		ans=max(ans,dp[i]);
	}
	printf("%d\n",ans);
	return ;
}

int main(){
	int t=read();
	while(t--)solve(); 
    return 0;
}

2.G - Yet Another RGB Sequence
题意是求字符串个数,字符串包含r个R,g个G,b个B,和k个RG
首先考虑安排k个RG,b个B,r-k个R,方案数=(k+b+rk)!1k!(rk)!b!
那么考虑g-k个G能填哪
不难发现有spots=(b+k+1)个空能填G,(B和RG的后面都可以填G,+1是因为开头也能填)
那么每个空可以填多个G,经典排列组合问题
x1+x2+····+xspots=gk,0xi
那么方案数=Cgk+spots1gk
所以答案就是ans=(k+b+rk)!1k!(rk)!b!Cgk+spots1gk

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const ll inf=2147383647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
ll power(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1)ans=ans*x%MOD;
		y>>=1;x=x*x%MOD;
	}
	return ans;
}
ll r,g,b,k;
vector<ll>fac(maxn),inv(maxn);
ll C(ll n,ll m){
	if(n==m || m==0)return 1;
	ll ans=fac[n]*inv[m]%MOD;
	return ans*inv[n-m]%MOD;
}
int main(){
	r=read();g=read();b=read();k=read();
	fac[0]=1;for(ll i=1;i<=r+g+b;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=power(fac[n],MOD-2);for(int i=n-1;i>=1;i--)inv[i]=inv[i+1]*(i+1)%MOD;inv[0]=1;
	ll ans=1;
	ans=ans*fac[k+r-k+b]%MOD;ans=ans*inv[k]%MOD*inv[r-k]%MOD*inv[b]%MOD; 
	ans=ans*C(g-k+b+k,g-k)%MOD;
	cout<<(ans%MOD+MOD)%MOD;
    return 0;
}

3.G. Two Merged Sequences
题意:是否能将序列分成严格递增和严格递减两个序列
题解:
我们可以发现,假如一个数被分进了上升序列,那它就是上升序列里面最大的数;如果分进下降序列,
它就是最小的数。所以,根据贪心的思路,假如我们把一个数分进了上升序列,我们只需要考虑在这种
情况下,下降序列最后一个数最大可以是多少。据此,考虑dp。
dp[i][0]表示前i个数,i是上升序列最后一个,前i-1个下降序列的最大值
dp[i][1]表示前i个数,i是下降序列最后一个,前i-1个上升序列的最小值
pre[i][0]表示第i个数是上升序列最后一个,第i-1个数是0/1(0表示是上升序列的,1是下降序列的),来记录方案

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e6+101;
const int MOD=998244353;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,a[maxn],dp[maxn][2],pre[maxn][2];
//dp[i][0]表示前i个数,i是上升序列最后一个,前i-1个下降序列的最大值
//dp[i][1]表示前i个数,i是下降序列最后一个,前i-1个上升序列的最小值
int main(){
	n=read();for(int i=1;i<=n;i++)a[i]=read(),dp[i][0]=-inf,dp[i][1]=inf;
	dp[1][0]=inf;dp[1][1]=-inf;
	for(int i=2;i<=n;i++){
		if(a[i]>a[i-1])dp[i][0]=dp[i-1][0],pre[i][0]=0;
		if(a[i]>dp[i-1][1] && a[i-1]>dp[i][0]){
			dp[i][0]=a[i-1];
			pre[i][0]=1;
		}
		if(a[i]<a[i-1])dp[i][1]=dp[i-1][1],pre[i][1]=1;
		if(a[i]<dp[i-1][0] && a[i-1]<dp[i][1]){
			dp[i][1]=a[i-1];
			pre[i][1]=0;
		}
	} 
	if(dp[n][0]==-inf && dp[n][1]==inf)puts("NO");
	else {	
		puts("Yes");
		int pos=0;
		if(dp[n][1]!=inf)pos=1;
		vector<int>ans(n+1);
		for(int i=n;i>=1;i--){
			if(pos==0)ans[i]=0;
			else ans[i]=1;
			pos=pre[i][pos];
		}
		for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
	}
    return 0;
}

4.B - Plus and AND
题意:每次操作,可以使一个ai变成ai+1,问最多M次操作,求k个数的AND和最大
题解:
不难发现是贪心题
对于这种异或贪心题都可以按照以下来写
每次判断ans+(1<<d)是否成立

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
vector<ll>a(maxn);
int n,m,k;
bool check(int x){
	vector<ll>b(n+1);
	for(int i=1;i<=n;i++){
		int j=30;
		while(j>=0 && (( (a[i]>>j) | ((~x)>>j) )&1) )j--;	//从高位到低位找到第一个位置j,x在j位置有1,a[i]在j位置没1
		if(j>=0){
			b[i]=(x&((1<<(j+1))-1))-(a[i]&((1<<j)-1));
		} 
	}
	sort(b.begin()+1,b.begin()+n+1);
	ll ans=0;
	for(int i=1;i<=k;i++)ans+=b[i];
	return ans<=m;
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++)a[i]=read();
	ll ans=0;
	for(int d=30;d>=0;d--){
		if(check(ans|(1<<d)))ans|=1<<d;
	}
	cout<<ans;
        return 0;
}

5.C. Square Subsets
题意:给一序列,问有多少种方案,使得选中的数乘起来是个完全平方数
题解:
考虑完全平方数的性质: 完全平方数x质因数分解,每个质因数的次方都是偶数
再考虑题目限制1ai70
那么70以内的质数只有19个
考虑把19个质因数转化成二进制表示形式,第i位=0/1表示第i个质数的次方是偶数还是奇数
所以我们可以写出一个暴力dp,设dpi,j表示序列中前i个数,选中数的乘积质因数分解后的状态位j的方案数
dpi,j=dpi1,j+dpi1,j xor mask[i]mask[i]a[i]
但时间复杂度超时
考虑如何优化?
因为1ai70,并且相同的数只有选偶数次和奇数次两种区别
那么我们不从a[1] a[n]枚举,我们从1 70枚举
poss[i][0]aiposs[i][1]ai
那么dpi,j=dpi1,jposs[i][0]+dpi1,j xor mask[i]poss[i][1]

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
ll power(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1)ans=ans*x%MOD;
		y>>=1;x=x*x%MOD;
	}
	return ans;
}
ll poss[71][2],dp[2][1<<20];
int n,bk[71],mask[71];
int pri[20]={2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int main(){
	n=read();
	for(int i=1;i<=n;i++)bk[read()]++;
	for(int i=1;i<71;i++){
		if(bk[i]==0){
			poss[i][0]=1;poss[i][1]=0;
			continue;
		}
		poss[i][0]=poss[i][1]=power(2,bk[i]-1); 
	}
	for(int i=1;i<71;i++){
		for(int j=0;j<19;j++){
			int cnt=0,now=i;
			while(now%pri[j]==0)now/=pri[j],cnt++;
			if(cnt&1)mask[i]|=1<<j;
		}
	}
	dp[0][0]=1;
	for(int i=1;i<71;i++){
		int ii=(i-1)%2;
		for(int j=0;j<(1<<19);j++){
			dp[i&1][j]=dp[ii][j]*poss[i][0]%MOD;
			dp[i&1][j]+=dp[ii][j^mask[i]]*poss[i][1]%MOD;
			dp[i&1][j]%=MOD;
		}
	}
	cout<<(dp[70&1][0]-1+MOD)%MOD;	//减1 都不选的情况 
    return 0;
}

6.E. Madoka and The Best University
题意:求lcm(gcd(a,b),c)a+b+c=n
题解:
在数论中求一个公式的解我们一定要选择好枚举方式,这样才能减少时间复杂度。
我们先考虑枚举顺序,如果我们先枚举a,再枚举b,这样可以确定c但是显然是n^2的,我们直接pass。枚举c显然显然没有任何道理可言,因此我们只能考虑枚举gcd(a, b)。
如果我们枚举gcd(a,b)=i的话,下一层循环就要枚举a + b,a + b的可能值是2i, 3i, 4i...这样的话时间复杂度就降到了O(nlogn)。但是如何计算答案呢?我们如何将a + b一种情况计算出所有的(a,b)可能情况的值呢?
首先此时,(a,b)是不能确定的,但是c可以确定的。我们可以列一个方程,设j=ki(2k),a=xb=jxgcd(a,b)==i>gcd(a/i,b/i)==1,此时代入方程就是 gcd(x/i,(jx)/i)==1>gcd(x/i,j/i)==1, 那么x/i的取值的可能就是与(j / i)互质且小于(j / i)的数,所以x的取值就是phi[j / i]。

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include <bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=4e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int gcd(int a,int b){
	if(b>a)swap(a,b);
	if(b==0)return a;
	return gcd(b,a%b);	
}
ll lcm(ll a,ll b){
	return a*b/gcd(a,b);
}	
int prime[maxn],tot,is[maxn],phi[maxn];
void get_phi(ll n){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!is[i])prime[++tot]=i,phi[i]=i-1;
        for(int j=1;j<=tot && prime[j]*i<=n;j++){
            is[i*prime[j]]=1;
            if(i%prime[j]==0){//说明已经有了一个prime[i]
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    return ;
}
int main(){
	int n=read();get_phi(n);
	ll ans=0;
	for(int i=1;i<=n;i++)for(int j=i*2;j<n;j+=i){
		ans+=lcm(i,n-j)*(ll)phi[j/i]%MOD;
		ans%=MOD;
	}
	printf("%lld\n",(ans+MOD)%MOD);
    return 0;
}

7.E - Erasing Vertices 2
题意、:给定一个无向图带有n个点m条边,每个点都有权值。每次要删除一条边,一次删除的代价是从这个点直接相邻的未被删除的点的权值和,使n次删除操作后的最大值最小,求其最小值。
题解:
总代价满足单调性,考虑二分
对于当前二分的值x
若某些点的相邻权值小于x,便可以删除,同时与它相邻的点的价值减小,若小于x那么也可以删除,用队列维护

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

int n,m,w[maxn]; 
vector<vector<int> >G(maxn);
ll a[maxn],b[maxn];
bool check(ll x){
	unordered_map<int,int>mm;queue<int>q;
	for(int i=1;i<=n;i++){
		if(a[i]<=x)q.push(i),mm[i]=1;
		b[i]=a[i];
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(auto v:G[u]){
			if(!mm[v]){
				b[v]-=w[u];
				if(b[v]<=x && !mm[v])q.push(v),mm[v]=1;
			}
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++)cnt+=mm[i];
	return n==cnt;
}
int main(){
	n=read();m=read();
	ll l=0,r=0;
	for(int i=1;i<=n;i++){
		w[i]=read();
		r+=(ll)w[i];
	}
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		G[x].pb(y);G[y].pb(x);
		a[x]+=(ll)w[y];
		a[y]+=(ll)w[x];
	} 
	ll ans=0;
	while(r>=l){
		ll mid=(l+r)>>1;
		if(check(mid))r=mid-1,ans=mid;
		else l=mid+1;
	}
	cout<<ans;
    return 0;
}

8.F - Exactly K Steps
题意:给一个n个点的树,q此询问,每次询问问与x点距离为k的点(任意输出一个),没有则输出-1
题解:
首先考虑x最大能延伸到哪?显然是树的直径的一个端点
那么我们就预处理出树的直径d1,d2
那么找距离为k的点就分别从x向d1和d2倍增找距离为k的点

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=4e5+101;
const int MOD=19980829;
const ll inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n;
struct wzq{
	int x,k,ans;
}q[maxn];
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int dep[maxn],f[maxn][21],d;
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	if(dep[x]>dep[d])d=x;
	for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x];i;i=nx[i]){		
		int v=to[i];if(v==fa)continue;
		f[v][0]=x;dfs(v,x);
	}
	return ;
}
int get_ans(int x,int k){
	for(int i=20;i>=0;i--){
		if((k&(1<<i)))x=f[x][i];
	}
	return x?x:-1;
}
int main(){
	n=read();
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	int Q=read();
	for(int i=1;i<=Q;i++)q[i].x=read(),q[i].k=read();
	dfs(1,0);
	for(int i=1;i<=n;i++){
		dep[i]=0;
		for(int j=0;j<=20;j++)f[i][j]=0;
	}
	dfs(d,0);
	for(int i=1;i<=Q;i++)q[i].ans=get_ans(q[i].x,q[i].k);
	for(int i=1;i<=n;i++){
		dep[i]=0;
		for(int j=0;j<=20;j++)f[i][j]=0;
	}
	dfs(d,0);
	for(int i=1;i<=Q;i++)q[i].ans=max(q[i].ans,get_ans(q[i].x,q[i].k)),cout<<q[i].ans<<endl;
    return 0;
}

9.Ex - Odd Sum
题意:给定一个长度为n的整数序列A,问有多少种不同方案,使得选中的数加起来为m
题解:
因为ai很小,所以我们每次枚举值来进行ntt

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
ll power(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1)ans=ans*x%MOD;
		y>>=1;x=x*x%MOD; 
	}
	return ans;
} 
ll fac[maxn],inv[maxn];
void init(int n){
	fac[0]=1;for(ll i=1;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=power(fac[n],MOD-2);for(ll i=n-1;i;i--)inv[i]=inv[i+1]*(i+1)%MOD;
	return ;
}
ll C(int m,int n){
	if(m==0 || n==m)return 1;
	ll ans=fac[n]*inv[m]%MOD;
	return ans*inv[n-m]%MOD;
}
typedef vector<ll> Poly;
int rev[maxn];
void get(int bit){
    for(int i=0;i<(1<<bit);i++)rev[i]=(rev[i>>1]>>1)|((1&i)<<(bit-1)); 
    return ;
}
void ntt(ll *a,int n,int f){
    get(log2(n));
	for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1){
        ll wn=power(3,(MOD-1)/(i<<1))%MOD;
        if(f==-1)wn=power(wn,MOD-2);
        for(int j=0;j<n;j+=i<<1){
            ll w=1,x,y;
            for(int k=0;k<i;k++,w=wn*w%MOD){
                x=a[k+j];y=a[k+j+i]*w%MOD;
                a[j+k]=(x+y)%MOD;a[j+k+i]=(x-y)%MOD; 
            }
            
        }
    }
    if(f==1)return ;
    int nv=power(n,MOD-2);  
    for(int i=0;i<n;i++)a[i]=a[i]*nv%MOD;
    return ;
}
ll F1[maxn],F2[maxn];
Poly mul(Poly A,Poly B){			//求多项式A*B 
    int n=A.size(),m=B.size(),lens=n+m-1;
    int bit=ceil(log2(lens));lens=(1<<bit);
    for(int i=0;i<lens;i++)F1[i]=F2[i]=0;
    for(int i=0;i<n;i++)F1[i]=A[i];
    for(int i=0;i<m;i++)F2[i]=B[i];
    ntt(F1,lens,1);ntt(F2,lens,1);
    for(int i=0;i<lens;i++)F1[i]=F1[i]*F2[i]%MOD;
    ntt(F1,lens,-1);
    Poly ans;
    for(int i=0;i<n+m-1;i++)ans.push_back(F1[i]);
    return ans;
}
int n,m,cnt[11]; 
int main(){
	n=read();m=read();init(1e6);
	for(int i=1;i<=n;i++)cnt[read()]++;
	Poly f_odd(1),f_even(1);
	//f_odd 选奇数个,f_even选偶数个 
	f_even[0]=1;
	int limi=1;
	for(int i=1;i<=10;i++){
		Poly g_odd(cnt[i]*i+1),g_even(cnt[i]*i+1);
		for(int j=1;j<=cnt[i];j+=2)g_odd[j*i]=C(j,cnt[i]);
		for(int j=0;j<=cnt[i];j+=2)g_even[j*i]=C(j,cnt[i]);
		
		Poly fff_odd,ff_odd;
		ff_odd=mul(f_odd,g_even);
		fff_odd=mul(f_even,g_odd);
		
		Poly fff_even,ff_even;
		ff_even=mul(f_even,g_even);
		fff_even=mul(f_odd,g_odd);
		
		limi+=cnt[i]*i;
		Poly now_f_odd(limi+1),now_f_even(limi+1);
		for(int j=0;j<=limi;j++){
			now_f_odd[j]=(ff_odd[j]+fff_odd[j])%MOD;
			now_f_even[j]=(ff_even[j]+fff_even[j])%MOD;
		}
		
		f_odd=now_f_odd;
		f_even=now_f_even;
	}
	if(m<f_odd.size())printf("%lld\n",(f_odd[m]%MOD+MOD)%MOD);
	else puts("0");
    return 0;
}

10.D. Letter Picking
题意:Alice 和 Bob 玩游戏,游戏规则是这样的:给定一个长度为偶数的小写字母字符串 s ,Alice 和 Bob 也有自己的字符串,最初是空的。 爱丽丝先手,然后他们交替操作。在一个操作中,玩家获取字符串 s 的第一个或最后一个字母,将其从 s 中删除,并将其添加到自己的字符串中的开头。直到字符串 s 为空,操作结束,谁最后得到的字符串字典序小,谁获胜。假设每个人都执行最优操作,那么最终的结局是 Alice 获胜,还是 Bob 获胜, 或者是平局(字符串相同)?
题解:
定义dpl,r表示:只考虑区间[l,r],先手的那个人在最优策略下执行完所有操作后结局是胜(用1表示),平局(用0表示),输(用-1表示)。
转移无非就4种情况:
对于区间[l,r]

  1. ALice选l,Bob选l+1或r
  2. Alice选r,Bob选l或r-1

先手固定后,后手的人有选择权,它会尽量的拿dp值小的
对于先手来说,一定选择dp值大的方案

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int dp[2002][2002]; 
string s;
int cal(int l,int r,int x /*Alice*/,int y){
	if(dp[l][r])return dp[l][r];
	if(s[x]>s[y])return -1;
	if(s[x]==s[y])return 0;
	return 1;
}
void solve(){
	cin>>s;int n=s.length();
	for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)dp[i][j]=0;
	for(int i=0;i<n-1;i++){
		if(s[i]!=s[i+1])dp[i][i+1]=1;
	}
	for(int len=4;len<=n;len+=2){
		for(int i=0;i<n;i++){
			int j=len+i-1;if(j>=n)break;
			int val1=inf,val2=inf;
			//先手取i,后手选i+1或j 
			val1=min(cal(i+1,j-1,i,j),cal(i+2,j,i,i+1));
			//先手取j,后手取i或j-1
			val2=min(cal(i+1,j-1,j,i),cal(i,j-2,j,j-1));
			dp[i][j]=max(val1,val2);
		}
	}
	if(dp[0][n-1]==1)puts("Alice");
	else if(dp[0][n-1]==0)puts("Draw");
	else puts("Bob"); 
	return ;
}

int main(){
	int t=read();
	while(t--)solve();
    return 0;
}
posted @   I_N_V  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示