【TC SRM 540 Div1】ProductQuery(区间dp)(记忆化搜索)(带权并查集)(中国剩余定理)

 

这神题,,好神

第一步我没想通就GG了。

题目求%10,根据中国剩余定理,我们可以把它分解为%2和%5,一个对应%2和%5的值唯一对应%10。

这样我们就可以将一个询问转成前缀积之比。

分成两个子问题处理,然后进行区间处理。使用记忆化搜索加速。

搜索边界是区间无询问了,那就什么情况都可以了,快速幂即可。

对于一个区间的询问,有两种可能:区间有包含询问为0的,和不包含的。

先区间dp一次把区间不含0的情况解决了,如果含0那在dp中就会return,如果不包含,可以直接用带权并查集判断合法性,进行区间dp。

如果包含,枚举第一个可能为0的位置。如果一个位置被一个非0询问包含那肯定不是0。

我们枚举了这第一个0的位置,左边就没有0了,直接区间dp。而右边却可能还有,那这又是一个子问题,继续记忆化搜索。

对于区间dp。

同样,如果没有询问限制了,那就返回快速幂。注意这个时候只有1或4种情况可以选择,因为不能包含0。

将所有询问插入并查集,并查集维护的是每个数是祖先的几倍。

更新在find之中进行。因为路径压缩,所以在改father之前,要把值乘上father(因为father也这么干了,所以father这时已经表示它是祖先的几倍),得到我是祖先的几倍,然后father改成祖先。

如果一个询问在一个联通块内,直接看当前询问答案是否等于key[r]:key[l-1]。

反之,要合并两个联通块,因为find了father,所以x此时的father就是x的祖先,y此时的father就是y祖先。将x祖先设为y祖先后,将x祖先的权值改成y/zx。这样以来下次findfather的时候,x的权值就可以更新成父亲的y/z倍,这与y的比值就合规了。

最后,查询区间有多少个联通块。这些联通块的头头可以随便取值。但注意,我们在做前缀积的时候将l-1用了进来,这个人的取值是一定的。所以最后返回的快速幂,只能是1或4的连通块数-1次方。

如此一来这道题就差不多了。

#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;
int ksm(int a,int b){
	int sum=1;
	while(b){
		if(b&1)sum=sum*a%mod;a=a*a%mod;b>>=1;
	}return sum;
}
int n,m;
struct node{
	int l,r,ans;
};
vector<node> gu;
vector<int> q1,q2,q3;
int f[103][103],g[103][103];
int b,inv[10];int ans;
int fa[103],key[103];
int find(int x){
	if(x==fa[x])return x;
	int t=fa[x];fa[x]=find(fa[x]);
	key[x]=key[x]*key[t]%b;return fa[x];
}
bool merge(int x,int y,int z){
	int fx=find(x),fy=find(y);
	if(fx!=fy){
		fa[fx]=fy;key[fx]=key[y]*inv[z]*inv[key[x]]%b;return true;
	} else{
		return key[y]*inv[key[x]]%b==z;
	}
}
int dp(int l,int r,vector<node> gu){
	if(gu.empty())return ksm(b-1,r-l+1);
	int ri=f[l][r];if(ri!=-1)return ri;
	for(int i=l-1;i<=r;i++)fa[i]=i,key[i]=1;
	for(int i=0;i<gu.size();i++){
		if(gu[i].ans==0)return f[l][r]=0;
		if(!merge(gu[i].l-1,gu[i].r,gu[i].ans))return f[l][r]=0;
	}
	int cnt=0;
	for(int i=l-1;i<=r;i++)if(fa[i]==i)cnt++;
	return f[l][r]=ksm(b-1,cnt-1);
}
int dfs(int l,int r,vector<node> gu){
	if(gu.empty())return ksm(b,r-l+1);
	int ri=g[l][r];if(ri!=-1)return ri;
	g[l][r]=ri=dp(l,r,gu);
	for(int i=l;i<=r;i++){
		int flag=1;vector<node> ll,rr;
		for(int j=0;j<gu.size();j++){
			if(gu[j].l<=i&&gu[j].r>=i)if(gu[j].ans!=0)flag=0;
			if(gu[j].r<i)ll.push_back(gu[j]);
			if(gu[j].l>i)rr.push_back(gu[j]);
		}if(!flag)continue;
		ri=(ri+dp(l,i-1,ll)*dfs(i+1,r,rr)%mod)%mod;
	}return g[l][r]=ri;
}
int query(int n){
	b=2;
	memset(f,-1,sizeof(f));memset(g,-1,sizeof(g));
	inv[1]=1;gu.clear();
	for(int i=0;i<q1.size();i++)gu.push_back((node){q1[i]+1,q2[i]+1,q3[i]%2});
	ans=dfs(1,n,gu);
	
	b=5;
	memset(f,-1,sizeof(f));memset(g,-1,sizeof(g));
	for(int i=1;i<5;i++)inv[i]=i*i*i%5;
	gu.clear();for(int i=0;i<q1.size();i++)gu.push_back((node){q1[i]+1,q2[i]+1,q3[i]%5});
	ans=dfs(1,n,gu)*ans%mod;
	return ans;
}
signed main(){
	n=in;m=in;int x,y,z;
	for(int i=1;i<=m;i++){
		x=in;y=in;z=in;
		q1.push_back(x);
		q2.push_back(y);
		q3.push_back(z);
		
	}cout<<query(n);
	return 0;
}

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值