[BZOJ2012][Ceoi2010]Pin 容斥

[BZOJ2012][Ceoi2010]Pin

第一次在考场上A的容斥题。

首先算仅d处不相同的字符串有几对就是求4-d处相同的字符串有几对。

d p [ i ] dp[i] dp[i]为仅有 i i i处相同的字符串有几对

假设我们要求仅有2处相同字符串对数,那么我们可以先枚举是哪两位相同,然后求出至少这两位相同的字符串对数,记为T。(这个过程可以通过map,字典树来实现(zz的我果断选择字典树))

然后我们可以发现仅有3处相同的字符串对被我们算了3遍( C 3 2 C^{2}_{3} C32),仅有4处相同的字符串对被我们算了6( C 4 2 C^{2}_{4} C42)遍,都要减掉。

于是 d p [ 2 ] = T − d p [ 3 ] ∗ 3 − d p [ 4 ] ∗ 6 dp[2]=T-dp[3]*3-dp[4]*6 dp[2]=Tdp[3]3dp[4]6

其他几个同理。

仅有0处相同的字符串的对数就是总对数减去 ( d p [ 1 ] + d p [ 2 ] + d p [ 3 ] + d p [ 4 ] ) (dp[1]+dp[2]+dp[3]+dp[4]) (dp[1]+dp[2]+dp[3]+dp[4])

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 50005
using namespace std;
char tmp[N][10];//tmp是原来的字符串 
char S[10];
int ID[10];
struct Tree{//这里拿字典树统计,也可以拿map来 
	int son[N*4][260];
	vector<int>way[N*4];
	int cnt[N*4];
	int tot;
	void clear(){
		tot=0;
		way[0].clear();
		memset(son[0],0,sizeof(son[0]));
		cnt[0]=0;
	}
	void Add(char *A){
		int now=0;
		cnt[0]++;
		for(int i=0;i<4;i++){
			int x=A[i];
			if(son[now][x]==0){
				son[now][x]=++tot;
				memset(son[tot],0,sizeof(son[tot]));
				cnt[tot]=0;
				way[tot].clear();
				way[now].push_back(x);
			}
			now=son[now][x];
			cnt[now]++;
		}
	}
	long long ans;
	void dfs(int now,int fa,int dep,int lim){
		if(dep==lim){
			ans+=1ll*cnt[now]*(cnt[now]-1);//统计有前lim个位置相同的字符串对数 
			return;
		}
		for(int i=0;i<(int)way[now].size();i++){
			int nxt=son[now][way[now][i]];
			dfs(nxt,now,dep+1,lim);
		}
	}
}ST;
bool mark[5555];
long long dp[10];
int n;
int num[10];
void Solve(int K){
	for(int i=1;i<=4;i++)ID[i]=i;
	memset(mark,0,sizeof(mark));
	long long T=0;
	do{
		int res=0;
		for(int i=1;i<=K;i++)num[i]=ID[i];
		sort(num+1,num+K+1);
		for(int i=1;i<=K;i++)res=res*10+num[i];//Hash判重 
		if(mark[res])continue;//上为枚举哪些位置相同 
		ST.clear();
		mark[res]=true;
		for(int i=1;i<=n;i++){
			for(int j=0;j<4;j++)S[j]=tmp[i][ID[j+1]-1];//把要相同的位置放到最前面 
			ST.Add(S);//加入字典树 
		}
		ST.ans=0;
		ST.dfs(0,0,0,K);
		T+=ST.ans;//统计答案 
	}while(next_permutation(ID+1,ID+5));
	T/=2;//注意要除以二(不然(1,2)和(2,1)就会算两对) 
	if(K==4)dp[K]=T;//式子们 
	else if(K==3){
		dp[K]=T-dp[4]*4;
	}else if(K==2){
		dp[K]=T-dp[3]*3-dp[4]*6;
	}else if(K==1){
		dp[K]=T-dp[2]*2-dp[3]*3-dp[4]*4;
	}else dp[K]=T-dp[1]-dp[2]-dp[3]-dp[4];
}
int main(){
	int d;
	ST.tot=0;
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;i++)scanf("%s",tmp[i]);
	for(int i=4;i>=0;i--)Solve(i);//一个一个算 
	printf("%lld\n",dp[4-d]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值