[NOIP2024] 遗失的赋值
首先考虑特殊情况,什么时候会无解,很明显当两个一元约束约束了同一个位置并且d值不同,那么必然是无解的,直接输出0就行
对与一般情况,我们一元约束的d值很明显是无用的,我们只用管c值就行,因为我们统计的是合法方案数,所以不论一元约束的d值是多少,对于答案的贡献是一定的
我们可以对一元约束按c值排序,可以将c值作为左右端点将序列分为m+1个区间,规定区间左端点受一元约束,右端点不受约束,即第i个区间的范围为
对于第一个区间(即左端不受一元约束的区间)中\(i\in[1,c_0-1]\) 时的 \(a_i\) 和 \(b_i\) 可以随便(夏季八)填,可以直接为答案产生 \(v ^{(c_0-1)*2}\)的贡献
对于第二个区间及以后,定义区间长度\(x=c_i-c_{i-1}\)我们从左端点即受约束的点开始考虑,对于\(c_{i-1}\)的位置有两种情况,要么\(a_{c_{i-1}}\not=d_{i-1}\)此时\([c_{i-1}+1,c_i-1]\)的位置都不受约束,可以夏季八填,此时a有v-1中填法,对于之后的\(2x-1\)个位置,有\(v^{2x-1}\)种填法,所以对答案的贡献为\((v-1)*v^{2x-1}\)
要么\(a_{c_{i-1}}=d_{i-1}\)此时a只有一中填法,b有v种填法,但此时对于\(c_{i-1}\)位置的约束会传递到\(c_{i-1}+1\)位置,我们需要对下一个位置分讨,重复这一过程,直到我们将约束传递到右端点,此时右端点的b只有一中填法,所以,一个区间的贡献为:
将左右两边的v合并,将式子合并即为\(\sum _{i=1} ^{i<=x} (v-1)*v^{2x-i} +v^{x-1}\)
将(v-1)拆开,我们发现中间的几项抵消掉后只剩下\(v^{2x}+v^{x-1}-v^x\)
我们只需要写个快速幂计算贡献即可
ACcode:
using namespace std;
#define ll long long
#define mod 1000000007
#define MAXN 100010
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct node{
ll c,d;
};
bool cmp(node a,node b){
return a.c<b.c;
}
struct node_dsu{
ll siz,p[MAXN];
node q[MAXN];
void insert(node a){
q[++siz]=a;
}
bool mk(){
sort(q+1,q+1+siz,cmp);
ll _siz=0;
for(int i=1;i<=siz;i++){
if(q[i].c==q[i+1].c){
if(q[i].d!=q[i+1].d)return false;
continue;
}
p[++_siz]=q[i].c;
q[i].c=q[i].d=0;
}
siz=_siz;
return true;
}
void clear(){
for(int i=1;i<=siz;i++)p[i]=0;
siz=0;
}
};
ll T,n,m,v,ans=1;
node_dsu dsu;
ll ksm(ll ds,ll zs){
ll now=1;
while(zs>0){
if(zs&1)now=(now*ds)%mod;
ds=(ds*ds)%mod;
zs/=2;
}
return now;
}
int main(){
T=read();
while(T--){
ans=1;
dsu.clear();
n=read(),m=read(),v=read();
for(int i=1;i<=m;i++){
dsu.insert((node){read(),read()});
}
if(dsu.mk()==false){
cout<<"0\n";
continue;
}
ans=ksm(v,2*(dsu.p[1]-1));
for(int i=1;i<dsu.siz;i++){
ll l=dsu.p[i+1]-dsu.p[i];
ans=ans*(ksm(v,l*2)%mod+mod-ksm(v,l)+ksm(v,l-1))%mod;
}
ans=ans*ksm(v,(n-dsu.p[dsu.siz])*2)%mod;
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号