codeforces367(字符串hash求lcp比较大小,Trie解最大异或问题)

12 篇文章 0 订阅
5 篇文章 0 订阅

C:

有n个字符串,排在一列,我们要求让这些字符串按字典序升序排列,对于一个字符串我们可以将它反转,有一个费用ci,求使升序排列的最小费用


dp+log len的比较大小


f【i】【0】表示不翻转,使前i个升序的最小费用

f【i】【1】表示反转,使前i个升序的最小费用

肯定是从f【i-1】【0】或【1】转移来的

只要判断大小就可以了


不过有个很关键的,文中说字符串的总长不超过100000;其实就是,就算用暴力的判断大小,均摊复杂度,也是线性的,也完全可以过,

然而我脑残的想写后缀数组,又怕麻烦,就写了字符串hash(不会的就用hash水,啦啦啦)


#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=100050;
const ll inf=1000000000000000;
ll f[N][2],c[N];
int sum[N],n,bb[N],m;
ull hs1[N],hs2[N],po[N];
char s[100000];

bool pan(int a,int k1,int b,int k2,int len)//if a.1~len == b.1~len return true
{
	ull h1=0,h2=0;
	
	if (k1==0)
	h1=hs1[sum[a-1]+len]-hs1[sum[a-1]]*po[len];
	else h1=hs2[sum[a]-len+1]-hs2[sum[a]+1]*po[len];
	
	if (k2==0)
	h2=hs1[sum[b-1]+len]-hs1[sum[b-1]]*po[len];
	else h2=hs2[sum[b]-len+1]-hs2[sum[b]+1]*po[len];
	
	return h1==h2;
}
bool pan(int a,int k1,int b,int k2)//if a>b return true;
{
	int ans=0,l=0,r=min(sum[a]-sum[a-1],sum[b]-sum[b-1]),mid;
	while (l<=r)
	{
		mid=(l+r)>>1;
		if (pan(a,k1,b,k2,mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	if (sum[b]-sum[b-1]==ans) return true;
	if (sum[a]-sum[a-1]==ans) return false;
	if (k1==0&&k2==0)
	{
		if (bb[sum[a-1]+ans+1]>bb[sum[b-1]+ans+1]) return true;
		else return false;
	}
	if (k1==0&&k2==1)
	{
		if (bb[sum[a-1]+ans+1]>bb[sum[b]-ans]) return true;
		else return false;
	}
	if (k1==1&&k2==0)
	{
		if (bb[sum[a]-ans]>bb[sum[b-1]+ans+1]) return true;
		else return false;
	}
	if (k1==1&&k2==1)
	{
		if (bb[sum[a]-ans]>bb[sum[b]-ans]) return true;
		else return false;
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%I64d",&c[i]);
	
	int len;
	
	for (int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		len=strlen(s+1);
		for (int j=1;j<=len;j++)
		bb[j+sum[i-1]]=s[j]-'a'+1;
		sum[i]=sum[i-1]+len;
	}
	
	po[0]=1;
	for (int i=1;i<=sum[n];i++) hs1[i]=hs1[i-1]*29+bb[i];
	for (int i=sum[n];i>=1;i--) hs2[i]=hs2[i+1]*29+bb[i];
	for (int i=1;i<N;i++) po[i]=po[i-1]*29;
	
	bool o=false;
	
	f[1][0]=0;f[1][1]=c[1];
	
	for (int i=2;i<=n;i++)
	{
		f[i][0]=inf;
		if (pan(i,0,i-1,0)) f[i][0]=min(f[i][0],f[i-1][0]);
		if (pan(i,0,i-1,1)) f[i][0]=min(f[i][0],f[i-1][1]);
		
		f[i][1]=inf;
		if (pan(i,1,i-1,0)) f[i][1]=min(f[i][1],f[i-1][0]+c[i]);
		if (pan(i,1,i-1,1)) f[i][1]=min(f[i][1],f[i-1][1]+c[i]);
		
		if (f[i][0]==inf&&f[i][1]==inf)
		{
			o=true;
			break;
		}
	}
	if (o) printf("-1");else 
	printf("%I64d",min(f[n][0],f[n][1]));
	return 0;
}


D:有一个多重集,支持插入一个数,删除一个数,给出一个数x,求集合中与它的最大异或值


突然发现,像求异或的问题,好多都通过异或Trie来求解,同时Trie还是算个数据结构,可以支持这各种操作


把每个数的二进制做为“字符串”,每一个都是32位,及以上,找最大时,就从上往下,贪心走当前位置和该数不相同的子节点,并加到答案里面

题解上说,像这样最大异或的题要开到32位,并且一定要开long long

注意开ll,有是10的9次方。。(注意在各种可能地方开ll)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;

int q,n,tot;
int ch[5000005][2],val[5000005];
bool v[5000500];
void insert(ll x)
{
	int k=0;
	for (int i=31;i>=0;i--)
	{
		ll id=x&(1<<i);
		if (id) id=1;else id=0;
		if (ch[k][id]==0) ch[k][id]=++n;
		k=ch[k][id];
		val[k]++;
	}
	v[k]=true;
}
void del(ll x)
{
	int k=0;
	for (int i=31;i>=0;i--)
	{
		ll id=x&(1<<i);
		if (id) id=1;else id=0;
		val[ch[k][id]]--;
		if (val[ch[k][id]]==0) 
		{
			ch[k][id]=0;
			break;
		}else k=ch[k][id];
	}
}
void work(ll x)
{
	ll ans=0;
	int k=0;
	for (int i=31;i>=0;i--)
	{
		ans<<=1;
		ll id=x&(1<<i);
		if (id) id=1;else id=0;
		if (ch[k][1-id]) ans=ans|1,k=ch[k][1-id];
		else k=ch[k][id];
	} 
	printf("%I64d\n",ans);
}
int main()
{
	scanf("%d",&q);
	char ch[6];
	ll x;
	insert(0);
	while (q--)
	{
		scanf("%s%I64d",ch,&x);
		switch(ch[0])
		{
			case '+':insert(x);break;
			case '-':del(x);break;
			case '?':work(x);break;
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值