CF1017D Solution

题目链接

题解

\(n\)的数字范围很小,又加之01串的条件,容易想到状压。01串的个数很少,一共只有\(2^{12}=4096\)个,而其余都是重复的,并且\(w,k\le 100\),可以想到将每个字符串在不同\(k\)时的答案预处理。具体方法受Trie树的启发,想到将集合\(S\)中的01串以二叉树存储,如题目中样例1可建立如下的树:

递归过程中,记录经过节点的权值和\(sum\)(设当前层数为\(pos\),如果01值不同权值为零,否则为\(w_{pos}\)),到达叶子节点时该01串\(k\)\(sum\)时的答案,可以加上叶子节点所指向\(S\)中01串的个数。如样例1中串\(00\),到达上方叶子节点时\(sum=40\),因此\(k=40\)\(00\)的答案\(+=2\)

AC题解

#include<bits/stdc++.h>
using namespace std;
const int N=15,M=5e5+10,S=(1<<12)+10;
struct node {char str[N]; int v,k,id;} a[M];
//a:询问——str:原01串,v:状压值,id:原下标 
char s[M][N]; 
int w[N],t[4*S][2],num[4*S],qwq[S][N*110],cnt,n;
//t:类Trie的二叉树,num:叶子节点01串数量,qwq[i][j]:状压值为i,k为j询问的答案,cnt:树中节点数量 
bool cmp(node a,node b) {return a.v<b.v;}
bool cmp2(node a,node b) {return a.id<b.id;}
void add(char *b)//将新01串加入树中
{
	int pos=0;
	for(int i=1;i<=n;i++)
	{
		bool ti=b[i]-'0';
		if(!t[pos][ti]) t[pos][ti]=++cnt;
		pos=t[pos][ti];
		if(i==n) num[pos]++;
	}
} 
void cul(node b,int x,int pos,int sum)//计算每个询问中01串的qwq
//x:树中下标,pos:串中下标,sum:Wu值总和 
{
	bool tmp=b.str[pos]-'0';
	if(pos==n) {qwq[b.v][sum]+=num[x]; return;}
	if(t[x][tmp]) cul(b,t[x][tmp],pos+1,sum+w[pos]);
	if(t[x][tmp^1]) cul(b,t[x][tmp^1],pos+1,sum);
}
int main()
{
	int m,q;
	scanf("%d%d%d",&n,&m,&q);
	for(int i=0;i<n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=m;i++) {scanf("%s",s[i]+1); add(s[i]);}
	for(int i=1;i<=q;i++) 
	{
		scanf("%s%d",a[i].str,&a[i].k); a[i].id=i;
		for(int j=0;j<n;j++) a[i].v+=(a[i].str[j]-'0')*(1<<j);//二进制转十进制(求状压值)
	}
	sort(a+1,a+q+1,cmp); a[0].v=-1;//使v相同询问在一起
	for(int i=1;i<=q;i++)
	{
		if(a[i].v==a[i-1].v) continue;
		cul(a[i],0,0,0);
	}
	for(int i=1;i<=q;i++)
	{
		if(a[i].v!=a[i-1].v)//>=j的询问均可以使用j的答案,因此需要累加
			for(int j=1;j<=100;j++) qwq[a[i].v][j]+=qwq[a[i].v][j-1];
	} 
	sort(a+1,a+q+1,cmp2);//按原序排序,以便输出
	for(int i=1;i<=q;i++) printf("%d\n",qwq[a[i].v][a[i].k]);
	return 0;
}
posted @ 2021-01-27 18:52  violet_holmes  阅读(41)  评论(0编辑  收藏  举报