SDOI2019 R2D2 题解

A 热闹的聚会与尴尬的聚会

第一问是个奇怪的东西,第二问求最大独立集,题目要求两问的答案都要尽量大。

如果你觉得这个题是纯乱搞,两问都不可做,那应该就没什么分数了。

考虑第一问的非 NP-hard 做法:贪心,删除度数最小的点,更新当前局面的答案,显然是一个先变大后减小的过程,可以用堆模拟。

第二问无脑随机即可,能过。

#include<stdio.h>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

int T,n,m;
vector<int> G[10005];

struct node {
	int a,b;
	node(int x=0,int y=0):a(x),b(y){}
};

bool operator < (node s,node t)
{
	return s.b>t.b;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1; i<=n; i++)
			G[i]=vector<int>();
		for(int i=1,u,v; i<=m; i++)
		{
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		vector<int> del,deg(n+1),vis(n+1);
		priority_queue<node> q;
		for(int i=1; i<=n; i++)
			q.push(node(i,deg[i]=G[i].size()));
		int ans=-1,num=0;
		while(!q.empty())
		{
			node t=q.top(); q.pop();
			if(vis[t.a])
				continue;
			if(t.b>ans)
				ans=t.b,num=del.size();
			del.push_back(t.a);
			vis[t.a]=1;
			for(auto &j:G[t.a])
				if(!vis[j])
				{
					--deg[j];
					q.push(node(j,deg[j]));
				}
		}
		printf("%d",(int)del.size()-num);
		for(int j=num; j<(int)del.size(); j++)
			printf(" %d",del[j]);
		puts("");
		vector<int>ord(n);
		for(int i=0; i<n; i++)ord[i]=i+1;
		vector<int>get;
		ans=-1;
		random_shuffle(ord.begin(),ord.end());
		vis=vector<int>(n+1);
		del=vector<int>();
		for(auto x:ord)
			if(!vis[x])
			{
				vis[x]=1;
				del.push_back(x);
				for(auto &j:G[x])vis[j]=1;
			}
		if((int)del.size()>ans)
		{
			ans=del.size();
			get=del;
		}
		random_shuffle(ord.begin(),ord.end());
		vis=vector<int>(n+1);
		del=vector<int>();
		for(auto x:ord)
			if(!vis[x])
			{
				vis[x]=1;
				del.push_back(x);
				for(auto &j:G[x])vis[j]=1;
			}
		if((int)del.size()>ans)
		{
			ans=del.size();
			get=del;
		}
		random_shuffle(ord.begin(),ord.end());
		vis=vector<int>(n+1);
		del=vector<int>();
		for(auto x:ord)
			if(!vis[x])
			{
				vis[x]=1;
				del.push_back(x);
				for(auto &j:G[x])vis[j]=1;
			}
		if((int)del.size()>ans)
		{
			ans=del.size();
			get=del;
		}
		random_shuffle(ord.begin(),ord.end());
		vis=vector<int>(n+1);
		del=vector<int>();
		for(auto x:ord)
			if(!vis[x])
			{
				vis[x]=1;
				del.push_back(x);
				for(auto &j:G[x])vis[j]=1;
			}
		if((int)del.size()>ans)
		{
			ans=del.size();
			get=del;
		}
		printf("%d",ans);
		for(auto x:get)
			printf(" %d",x);
		puts("");
	}
	return 0;
}

B 移动金币

我们记第 (i) 堆石子的数目为从右手数第 (i) 个棋子到第 (i+1) 个棋子(或者右边界)的距离。
这就是一个阶梯 Nim 模型。
阶梯 Nim 的 SG 函数等于奇数位置的石子的 SG 值的异或,别的位置没有意义,
那么可以考虑 dp 出 (f_k) 表示 (k) 个石子放在奇数位置且异或和 (=0) 的方案数,
然后用组合数把剩下来的石子插入到中间即可。
可以按二进制位逐个背包,复杂度容易写到 (O(nklog n)) 或更低。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
const int mod=1000000009,SIZE=1<<18;
inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;}
inline int dec(int a,int b){return (a-=b)<0?a+mod:a;}
inline int mul(int a,int b){static ll r;r=(ll)a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
	for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));
	return res;
}
inline void Inc(int &a,int b){(a+=b)>=mod&&(a-=mod);}
inline void Dec(int &a,int b){(a-=b)<0&&(a+=mod);}
inline void Mul(int &a,int b){a=mul(a,b);}

int n,m,ans,C[55][55],fac[SIZE],ifac[SIZE],inv[SIZE],f[SIZE];

int choose(int n,int m){
	return mul(fac[n],mul(ifac[n-m],ifac[m]));
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<=m;++i)
		for(int j=C[i][0]=1;j<=i;++j)
			C[i][j]=add(C[i-1][j-1],C[i-1][j]);
	inv[0]=inv[1]=ifac[0]=ifac[1]=fac[0]=fac[1]=1;
	for(int i=2;i<SIZE;++i){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(inv[mod%i],mod-mod/i);
		ifac[i]=mul(ifac[i-1],inv[i]);
	}
	int c=(m+1)/2,r=m+1-c;
	int d=n-m;
	f[0]=1;
	for(int k=1;k<=d;k<<=1)
		for(int i=d;i>=k<<1;--i)
			for(int j=2;i>=j*k&&j<=c;j+=2)
				Inc(f[i],mul(f[i-j*k],C[c][j]));
	for(int s=0;s<=d;++s)Inc(ans,mul(f[s],choose(d-s+r-1,r-1)));
	printf("%d",dec(choose(n,m),ans));
	return 0;
}

C 连续子序列

0
01
0110
01101001
0110100110010110

随便划出一个区间作映射 (<01 ightarrow 0,10 ightarrow 1>) 都能递归到上一层,长度缩一半,映射关系是双向且唯一的。
(f(str,k)) 表示串 (str) 跟着 (k) 个字符能匹配的方案数然后就每次长度缩一半了。

可以证明当 (|S|+k>3) 时有唯一切割方案,也就是 (dfs) 两边下去有至少一个是 (0),所以不用管算重。
(|S|+kle 3) 的情况特判边界 return 即可。

#include<map>
#include<iostream>
#include<string>
#include<algorithm>
#define SZ(x) ((int)(x).size())
using namespace std;

typedef long long ll;
const int P=1000000009;

map<pair<string,ll>,int> mem;

int dfs(const string&s,ll k)
{
	if(SZ(s)==1)
	{
		if(k==0)
			return 1;
		if(k==1)
			return 2;
		if(k==2)
			return 3; //!!!
	}
	if(SZ(s)==2)
	{
		if(k==0)
			return 1;
		if(k==1)
			return s[0]==s[1]?1:2;
	}
	if(SZ(s)==3&&!k)
		return !(s[0]==s[1]&&s[1]==s[2]); //!!!
	auto now=make_pair(s,k);
	if(mem.count(now))
		return mem[now];
	string d;
	int ans=0,ok=1;
	for(int i=0; i<SZ(s); i+=2)
	{
		if(s[i]==s[i+1])
		{
			ok=0;
			break;
		}
		d+=s[i];
	}
	if(ok)
		ans=(ans+dfs(d,(k-SZ(s)%2+1)/2))%P;
	ok=1;
	d=s[0]^1;
	for(int i=1; i<SZ(s); i+=2)
	{
		if(s[i]==s[i+1])
		{
			ok=0;
			break;
		}
		d+=s[i];
	}
	if(ok)
		ans=(ans+dfs(d,(k-(SZ(s)-1)%2+1)/2))%P;
	mem[now]=ans;
	return ans;
}

int main()
{
	int t; string s; ll k;
	for(cin>>t;t;--t)
		cin>>s>>k,cout<<dfs(s,k)<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/bestwyj/p/13023222.html