csp-s模拟【模拟】【倍增lca+计数】【dp+kmp】

woj4793~4795

今天的难度还真csp

盘王节

题意明晰:对方有御符,兵符,我有兵符。

一旦我的某个兵符攻击力大于等于对方的某个符,可以打碎那个符。

攻击方式有2:

①:打碎对面所有御符,然后可以选择直接攻击对方或者攻击对方兵符(直接攻击获得我的能力值大小伤害)。

②:攻击兵符,获得我和对方兵符能力差的伤害,同时击碎兵符。

考场60分,因为没有考虑打碎御符后可以攻击对方兵符。

 

注意能力有负数。意味着有时候攻击对方兵符比直接攻击对方要赚的多一些。

分两种情况考虑。一种是击碎对方全部御符(当然打不完就只能continue),然后击碎对方的负数兵符(获得更大贡献),然后获得我的所有正数贡献。(当然我的负数不用最好(但是我的负数可以拿来打碎对方负数的兵符))

一种是直接击碎兵符。当然从对方最小的开始,然后该怎么算怎么算。

写的很混乱,,

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int n,m1,m2;
struct node{
	int key,cnt;
}a[1000003],b[1000003],c[1000003];
bool cm(node a,node b){
	return a.key<b.key;
}
bool cmm(node a,node b){
	return a.key>b.key;
}
//clock_t st,ed;
int ans;int res[1000003],resc[1000003];
int sum,sumabs,ssabs;
signed main(){
//	freopen("panwang3.in","r",stdin);
//	freopen("panwang.out","w",stdout);
	//st=clock();
	n=in;m1=in;m2=in;
	for(register int i=1;i<=n;++i)a[i].key=in,a[i].cnt=in;//sumabs+=(a[i].key>0)?(a[i].key*a[i].cnt):0;
	//cout<<"#";
	for(register int i=1;i<=n;++i)if(a[i].key>0)sumabs+=a[i].key*a[i].cnt;ssabs=sumabs;
	for(register int i=1;i<=m1;++i)b[i].key=in,b[i].cnt=in;
	for(register int i=1;i<=m2;++i)c[i].key=in,c[i].cnt=in;
	//cout<<"#";
	sort(a+1,a+n+1,cm);sort(b+1,b+m1+1,cm);sort(c+1,c+m2+1,cm);//cout<<"#";
	//cout<<"#";
	for(register int i=1,j=1;i<=n&&j<=m1;){
		//cout<<i<<" "<<j<<endl;
		while(a[i].key<b[j].key&&i<=n)++i;
		if(i>n)break;
		//if(sumabs<=0)break;
		int x=min(a[i].cnt,b[j].cnt);
		//cout<<x<<" "<<e[i].key<<" "<<e[i].cnt<<" "<<d[j].key<<" "<<d[j].cnt<<endl;
		a[i].cnt-=x;
		b[j].cnt-=x;res[i]+=x;
		if(a[i].key>0)sumabs-=x*a[i].key;
		if(!b[j].cnt)++j;if(!a[i].cnt)++i;
	}//没考虑对方攻击为负数情况。
	//a[++n].key=0;a[n].cnt=0x3f3f3f3f;sort
	if(!b[m1].cnt){
		int gusum=0;
		for(int i=n,j=1;i>=1&&j<=m2;){
			if(a[i].key<=c[j].key)break;
			if(c[j].key>=0)break;
			int x=min(a[i].cnt,c[j].cnt);
			a[i].cnt-=x;c[j].cnt-=x;resc[j]+=x;res[i]+=x;
			sumabs-=x*a[i].key;gusum+=x*a[i].key-x*c[j].key;
			if(!a[i].cnt)--i;if(!c[j].cnt)++j;
		} 
	//	for(int i=1;i<=m2;i++)if(c[i].key<0)gusum+=c[i].key*c[i].cnt;
		ans=max(ans,sumabs+gusum);//cout<<"#";
	}
	
	for(register int i=1;i<=n;++i)a[i].cnt+=res[i];
	for(int i=1;i<=m2;i++)c[i].cnt+=resc[i];
	sort(a+1,a+n+1,cmm);
	int i=1,j=1;int sumnow=0;
	for(;i<=n&&j<=m2;){
		if(a[i].key<=c[j].key)break;
		int x=min(a[i].cnt,c[j].cnt);
		sumnow+=x*(a[i].key-c[j].key);
		a[i].cnt-=x;c[j].cnt-=x;//ssabs-=x*a[i].key;
		if(!a[i].cnt)++i;if(!c[j].cnt)++j;
	}
	ans=max(ans,sumnow);
	cout<<ans;
	//ed=clock();
	//cout<<" "<<ed-st;
	
	
	return 0;
}

祝箸节

要求给边染色,黑白两色。一个合法的最小生成树必须包含两种颜色的边。求染色方案数,使得合法最小生成树权值为给定的一个值X。

首先要一个结论。

结论:任何一种合法的最小生成树,在形态上只与原图最小生成树有最多一条边的差距。

简单证明:首先,如果最小生成树合法,那不用修改。

反之,最小生成树不合法,说明该MST边色相同。不妨都设为黑色。

如果新加的非树边要合法,则必须为白边。否则没有贡献。

此时原树变成了一棵含有1条白边,n-2条黑边的树。

此时再添加任何一条白边,都无法进入树。因为树已经合法。证毕(证了个寂寞,,乱写一通)

 

根据上述结论,你可以先做一次最小生成树。如果当前权值已经大于X,自然答案为0。

反之,对于每条非树边,求出强制包含这条边的不严格次小生成树,这个可以直接树上倍增做。环上减去最大边就行。

对于次小生成树的权值,分两种情况讨论。

如果该次小生成树权值等于X,记为equ++。如果小于,记为les++。大于不管。

 

然后对于最初的sum,也要讨论。

如果最初的sum已经等于X,那新加的边也只能是equ。

根据刚刚结论,有n-1条最小生成树边同色,而剩下的边中,有equ条边,这些边不能全都和MST同色。

容斥一下,总方案减去非法方案(即equ+n-1条边都同色),得到

Ans=2^m-2*(2^{m-(n-1)-equ})

这个2是因为颜色可以同时取反。

 

如果sum小于X,那我们在取能使答案合法的equ的同时,那些les边都要和MST同色,否则答案不会取equ。

使用容斥,Ans=2*(2^{m-(n-1)-les}-2^{m-(n-1)-les-equ})

然后做完了。记得memset之类的东西。

变量名小心,我用的结构体(有点恶心)

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int n,m,T,X;
struct bcj{
	int fa[100003];
	void clear(){for(int i=1;i<=n;i++)fa[i]=i;}
	int find(int x){if(x==fa[x])return x;return fa[x]=find(fa[x]);}
}bcj;
struct tree{
	int dep[100003],fa[100003][22],maxx[100003][22];
	int first[100003],nxt[200003],to[200003],w[200003],tot;
	void add(int a,int b,int c){
		nxt[++tot]=first[a];first[a]=tot;to[tot]=b;w[tot]=c;
		nxt[++tot]=first[b];first[b]=tot;to[tot]=a;w[tot]=c;
	}
	void clear(){memset(first,0,sizeof(first));tot=0;}
	void dfs(int u,int faa){
		dep[u]=dep[faa]+1;
		fa[u][0]=faa;for(int i=1;i<=19;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
		for(int i=1;i<=19;i++)maxx[u][i]=max(maxx[u][i-1],maxx[fa[u][i-1]][i-1]);
		for(int i=first[u];i;i=nxt[i]){
			int v=to[i];if(v==faa)continue;
			maxx[v][0]=w[i];dfs(v,u);
		}
	}
	int query(int a,int b){
		if(dep[a]<dep[b])swap(a,b);
		int gu=0;
		for(int i=19;i>=0;i--){
			if(dep[fa[a][i]]>=dep[b]){
				gu=max(gu,maxx[a][i]);a=fa[a][i];
			}
		}if(a==b)return gu;
		for(int i=19;i>=0;i--){
			if(fa[a][i]!=fa[b][i]){
				gu=max(gu,max(maxx[a][i],maxx[b][i]));
				a=fa[a][i],b=fa[b][i]; 
			}
		}return max(gu,max(maxx[a][0],maxx[b][0]));
	}
}tree;
struct node{
	int u,v,w,flag;
	void clear(){
		flag=0;
	}
}edge[200003];
bool cm(node a,node b){
	return a.w<b.w;
}
int sum,cnt;
int les,equ;
const int mod=1e9+7;
int ksm(int a,int b){
	int gu=1;
	while(b){//cout<<b<<endl;
		if(b&1)gu=gu*a%mod;a=a*a%mod;b>>=1;
	}return gu;
}
void solve(){
	//for(int i=1;i<=m;i++)cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;
	sum=cnt=les=equ=0;
	for(int i=1;i<=m;i++){//cout<<"#";
		int x=bcj.find(edge[i].u),y=bcj.find(edge[i].v);//cout<<" "<<x<<" "<<y<<endl;
		if(x!=y){
		//	cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;
			++cnt;sum+=edge[i].w;bcj.fa[x]=y;edge[i].flag=1;
			tree.add(edge[i].u,edge[i].v,edge[i].w);
			if(cnt==n-1)break;
		}
	}
	//cout<<"Sum "<<sum<<endl;
	if(sum>X){cout<<"0"<<'\n';return;}
	tree.dfs(1,0);
	int now=sum;
	for(int i=1;i<=m;i++){
		if(!edge[i].flag){
			now=sum+edge[i].w-tree.query(edge[i].u,edge[i].v);
			if(now==X)equ++;if(now<X)les++;
		}
	}
	int ans=0;
	//cout<<les<<" "<<equ<<endl;
	if(sum==X){
		ans+=(ksm(2,m)-2*ksm(2,m-n+1-equ)%mod+mod)%mod;ans%=mod;
	}else{
		ans+=2*(ksm(2,m-n+1-les)-ksm(2,m-n+1-les-equ)+mod)%mod;ans%=mod;
	}
	cout<<ans<<'\n';
}
signed main(){
	T=in;
	while(T--){
		n=in;m=in;X=in;
		bcj.clear();
		tree.clear();
		for(int i=1;i<=m;i++){
			edge[i].flag=0;
			edge[i].u=in;edge[i].v=in;edge[i].w=in;
			
		}sort(edge+1,edge+m+1,cm);
		//for(int i=1;i<=m;i++)cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].w<<endl;	
		solve();
	}
	return 0;
}

耍望节

今日神题。但也没太神。

从问题入手,问第k大合法串,k还很大,联想上次dzy出的那个倍增题(当然这道题的在线做法是倍增),可以大致感受这是一个dp题。

很明显,我们要像trie树那样潇洒走size的话,我们要求得的是,后面的合法方案数。这类似于trie树上dp,求子树size的那种。

然而,因为问号的存在,我们只用长串走到第i位,是不能限定一个结点的。

所以我们用f[i][j]表示长串走到第i位,当前匹配了短串的第j位的,i+1~n位填出来合法的方案数。

所以这是一个后缀方案数。

首先考虑转移:考虑第i+1位填什么,如果是给定数字只能填给定数字。但如果是问号就什么都可以填。

问题来了,填了这个数后,你和短串的匹配状态会变化。打断了匹配,并不一定是从头开始,你可能和前面的几个字母构成了短串的一个前缀。

这个前后缀的问题,,kmp自动机伺候一下。令fail[i][j]表示短串第i位,和短串匹配到第j位,应该跳到的最长前后缀的那个地方,跟next一样,该怎么求怎么求,只是注意超出边界的fail赋成边界。这方便dp。看代码。

所以我们得到f[i][j]=\sum(f[i+1][fail[j][k]])[k\subset I+1],大写I+1表示i+1位能填的数字。

边界f[n][m]=1;这个时候已经填完了,方案为1。

然后就可以反着dp了。

 

官方题解是在线倍增求第k大。

我是离线的。

如果我真在线做,复杂度nq,爆炸。

但是,我们发现,使得状态变多的罪魁祸首是问号。而一个问号往往意味着方案*10级别的增长。

去掉最后18个问号,因为m的长度是20,所以一共有38个问号可以影响答案。超过38个,自然对我们答案不能构成影响。

所以我们可以离线问题一遍dfs。

modify20191105 20:30 :巨神hzy和dhy提供了一组hack数据把本做法hack了,,我要去看题解了qwq!不过dhy提供了一种优化方法目前不知可不可行,正在(漫漫无期地)尝试中。

hack方法:在大串开头加一堆问号,然后GG。

(如果是随机数据,每个点只有十一分之一的几率出问号,如果前面出一堆问号的话,,是很可能卡掉我的做法的。不过为什么没卡掉还run得那么快是个问题,值得研究一下,有利于下次比赛乱搞随机化踩标算更好的分析复杂度)

//guess1:从前向后dp。
//  FAIL:未知是否成功,无法正向搜索得到答案,中间状态无成功匹配的意义。
//guess2:从后向前dp。
// idea1:f[i][j]表示长串1~i都填好,短串弄到第j位的,后面合法方案数。因为是后面合法,所以
//        枚举下一位填的是什么(只能是对应数字,或者下一位为?)。
//        如果下一位为?,应当什么都可以填。故枚举下一位填甚。反之只有一个选择。 
//        f[i][j]=sum(f[i+1][fail[j][x]]); 使用kmp自动机求得fail(选择下一个可能打破选择) 。
//  prob:方案数过大,不得取模。
//*idea*:发现k最大1e18。远小于最大总方案数1e(5e5)。故与1e18取min,如果等于inf,表示超出考虑范围。
//  prob: 询问过多,如何处理。
//*idea*: 离线dfs。从小到大。反正对于每个询问,最多产生不到20次搜索分支。
//  finish?

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
const int mod=1e9+7,inf=1e18+7;
int f[50003][22];
char ch[50003];int a[50003],b[22];
int nxt[22],fail[22][12];
int T,n,q,lena,lenb;
struct node{
	int k,id;
}que[100003];
bool cm(node a,node b){
	return a.k<b.k;
}
void add(int &a,int b){
	a=(a+b);a=min(a,inf);
}
void getfail(){
	memset(nxt,0,sizeof(nxt));memset(fail,0,sizeof(fail));
	for(int i=2;i<=lenb;i++){
		int j=nxt[i-1];while(j&&b[j+1]!=b[i])j=nxt[j];
		nxt[i]=(b[j+1]==b[i])?(j+1):0;
	}
	for(int i=0;i<=lenb;i++){
		for(int j=0;j<10;j++){
			if(i==lenb)fail[i][j]=lenb;
			else{
				int k=i;while(k&&b[k+1]!=j)k=nxt[k];
				fail[i][j]=(b[k+1]==j)?(k+1):0;
				
			}
		}
	}
}int now;int ans[100003],pos;
void getans(int apos,int bpos,int Rank,int key){
	if(pos>now)return;
	if(apos>n){
		while(que[pos].k==Rank+1&&pos<=now)ans[que[pos++].id]=key;return;
	}
	for(int i=0;i<10;i++){
		if(a[apos]==i||a[apos]==233){
			if(f[apos][fail[bpos][i]]+Rank>=que[pos].k)getans(apos+1,fail[bpos][i],Rank,((key*10)%mod+i)%mod);
			if(pos>now||f[apos][fail[bpos][i]]==inf)return;
			Rank+=f[apos][fail[bpos][i]];
		}
	}
}
signed main(){
//	freopen("shuawang.in","r",stdin);
//	freopen("shuawang.out","w",stdout);
	T=in;
	while(T--){
		n=in;now=q=in;scanf("%s",ch+1);lenb=strlen(ch+1);
		for(int i=1;i<=lenb;i++)b[i]=ch[i]-'0';pos=1;
		scanf("%s",ch+1);//=strlen(a+1);
		for(int i=1;i<=n;i++){
			if(ch[i]=='?')a[i]=233;else a[i]=ch[i]-'0';
		}
//		for(int i=1;i<=n;i++)cout<<a[i]<<" ";cout<<endl;
//		for(int i=1;i<=lenb;i++)cout<<b[i];cout<<endl;
		getfail();
//		for(int i=1;i<=lenb;i++)cout<<nxt[i]<<" ";cout<<endl;
		memset(f,0,sizeof(f));f[n][lenb]=1;
		for(int i=n-1;i>=0;i--){
			for(int j=0;j<=lenb;j++){
				for(int k=0;k<10;k++){
					if(a[i+1]==k||a[i+1]==233){
						add(f[i][j],f[i+1][fail[j][k]]);
					}
				}
			}
		}
//		for(int i=0;i<=n;i++){
//			for(int j=0;j<=lenb;j++)cout<<f[i][j]<<" ";cout<<endl;
//		}
//		for(int i=1;i<=lenb;i++){
//			for(int j=0;j<10;j++)cout<<fail[i][j]<<" ";cout<<endl;
//		}
		for(int i=1;i<=q;i++){
			que[i].k=in;que[i].id=i;
		}sort(que+1,que+q+1,cm);
		while(now&&que[now].k>f[0][0]){
			ans[que[now--].id]=-1;
		}getans(1,0,0,0);
		for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
	}
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值