[冲刺国赛2022] 模拟赛4
亿些整理
题目描述
给定 \(n\times n\) 的矩阵 \(a_{i,j}\),如果 \(a_{i,j}\not=-1\) 代表二分图的左部点 \(i\) 向右部点 \(j\) 有权值为 \(a_{i,j}\) 的边。
问是否存在一个完美匹配使得边的权值和为 \(k\) 的倍数,只需要判断,不需要构造方案。
\(n,k\leq 100,-1\leq a_{i,j}<k\)
解法
其实这题跟二分图根本就没关系,考虑找出一个排列 \(p\),使得 \(\forall i,a_{i,p_i}\not=-1\),并且 \(\sum a_{i,p_i}\bmod k=0\)
既然是在矩阵上找排列的问题,可以自然地联想到行列式。由于本题需要表示 \(\sum a_{i,p_i}\bmod k\),所以我们用生成函数来表示它,也就是令第 \(i\) 个位置上的多项式为 \(F_{i,j}(x)=x^{a_{i,j}}\)(特别地,如果 \(a_{i,j}=-1\) 那么 \(F_{i,j}(x)=0\))
定义多项式乘法为模 \(x^k\) 意义下的循环卷积,那么我们只需要求出行列式 \(det\)(也是一个 \(k-1\) 次多项式),如果 \([x^0]det>0\) 说明无解。但是由于行列式前面 \((-1)^{\pi(p)}\) 的系数,可能有解的情况会抵消成 \(0\),那么我们在多项式前面添加系数,就可以保证非常高的正确率了。
问题是怎么求出这个行列式?如果暴力多项式操作是 \(O(n^4\log n)\) 的(我考试时候就写的这个,\(\tt T\) 飞了),但是发现可以求出 \(k\) 个点值,然后插值就可以得到 \(det\),那么这 \(k\) 个点就选择单位根,因为它们契合循环卷积的性质。
对于每个 \(k\),都需要预处理出 \(k|(p-1)\) 的大质数 \(p\),并且计算出它们的原根。时间复杂度 \(O(n^4)\),根据单位根反演的知识,并不需要写拉格朗日插值,只需要把所有点值加起来就可以判断 \([x^0]det\) 是否 \(>0\) 了,不过这都不重要。
#include <cstdio>
#include <random>
#include <iostream>
using namespace std;
const int N = 105;
const int M[] = { 0, 0, 1073741827, 1073741827, 1073741833, 1073741831, 1073741827,
1073741831, 1073741833, 1073741833, 1073741831, 1073741857, 1073741833, 1073741839,
1073741831, 1073741971, 1073741857, 1073742169, 1073741833, 1073741833, 1073742361,
1073741971, 1073741857, 1073741891, 1073741833, 1073742851, 1073741839, 1073741833,
1073742209, 1073742053, 1073741971, 1073742289, 1073741857, 1073741857, 1073742169,
1073741831, 1073741833, 1073742073, 1073741833, 1073741839, 1073742361, 1073742113,
1073741971, 1073741993, 1073741857, 1073742391, 1073741891, 1073742073, 1073741857,
1073742391, 1073742851, 1073742169, 1073741969, 1073742053, 1073741833, 1073742671,
1073742209, 1073741833, 1073742053, 1073741827, 1073742361, 1073747621, 1073742289,
1073742391, 1073741953, 1073741891, 1073741857, 1073742403, 1073742169, 1073742259,
1073741831, 1073745781, 1073741833, 1073743861, 1073742073, 1073743051, 1073741833,
1073742209, 1073741839, 1073742721, 1073742721, 1073741833, 1073742113, 1073745769,
1073742517, 1073743291, 1073741993, 1073742169, 1073741857, 1073743883, 1073742391,
1073742671, 1073742673, 1073742289, 1073742073, 1073744911, 1073741857, 1073742277,
1073742391, 1073742517, 1073743501 };
const int G[] = { 0, 0, 2, 2, 5, 13, 2, 13, 5, 5, 13, 5, 5, 3, 13, 2, 5, 7, 5, 5, 7, 2, 5, 6, 5, 2,
3, 5, 3, 2, 2, 37, 5, 5, 7, 13, 5, 5, 5, 3, 7, 5, 2, 3, 5, 6, 6, 5, 5, 6, 2, 7,
3, 2, 5, 7, 3, 5, 2, 2, 7, 2, 37, 6, 10, 6, 5, 2, 7, 2, 13, 2, 5, 6, 5, 2, 5, 3,
3, 11, 11, 5, 5, 7, 6, 2, 3, 7, 5, 2, 6, 7, 5, 37, 5, 6, 5, 5, 6, 6, 2 };
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,ans,MOD,a[N][N],b[N][N],g[N][N];
void add(int &x,int y) {x=(x+y)%MOD;}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
int det()
{
int r=1;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
if(!g[i][i] && g[j][i])
{
r=MOD-r;
swap(g[i],g[j]);
break;
}
if(!g[i][i]) return 0;
r=r*g[i][i]%MOD;
for(int j=i+1;j<=n;j++)
{
int t=g[j][i]*qkpow(g[i][i],MOD-2)%MOD;
for(int k=i;k<=n;k++)
add(g[j][k],MOD-g[i][k]*t%MOD);
}
}
return r;
}
signed main()
{
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
n=read();k=read();MOD=M[k];
mt19937 z(114514);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=read(),b[i][j]=z()%MOD;
for(int i=0;i<k;i++)
{
static int pw[N]={};
pw[0]=1;pw[1]=qkpow(G[k],(MOD-1)/k*i);
for(int j=2;j<k;j++) pw[j]=pw[j-1]*pw[1]%MOD;
for(int x=1;x<=n;x++)
for(int y=1;y<=n;y++)
g[x][y]=~a[x][y]?pw[a[x][y]]*b[x][y]%MOD:0;
add(ans,det());
}
if(ans) puts("Yes");
else puts("No");
}
亿块田
题目描述
维护一个长度为 \(n\) 的序列 \(a\),初始值给定,有 \(m\) 次询问,需要支持这三种操作:
1 l r x
,把 \(i\in[l,r]\) 的 \(a_i\) 按位与上 \(x\)2 l r x
,把 \(i\in[l,r]\) 的 \(a_i\) 按位或上 \(x\)3 l r
,询问 \(i\in[l,r]\) 的 \(a_i\) 的最大值。
\(n,m\leq 2\cdot 10^5,a_i,x<2^{20}\)
解法
考虑按位修改,与的效果就是 不修改 \(/\) 将它们全部变成 \(0\);或的效果就是 不修改 \(/\) 将他们全部变成 \(1\)
发现这东西很像颜色段均摊,我们那个 \(\tt set\) 分别维护每一位 \(0/1\) 的连续段,然后就可以转化成区间加问题。
但是 \(\tt set\) 的常数太大了,我们考虑把这两个过程都放在线段树上,也就是对于一个区间,如果与的效果 \(/\) 或的效果对于每个点都是一致的,那么直接打加法标记。这个可以维护区间与和区间或来判断,时间复杂度基于颜色段均摊,就是 \(O(n\log ^2n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 800005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,mx[M],fl[M],sa[M],so[M];
void up(int i)
{
mx[i]=max(mx[i<<1],mx[i<<1|1]);
sa[i]=sa[i<<1]&sa[i<<1|1];
so[i]=so[i<<1]|so[i<<1|1];
}
void build(int i,int l,int r)
{
if(l==r)
{
mx[i]=sa[i]=so[i]=read();
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
up(i);
}
void add(int i,int c)
{
mx[i]+=c;fl[i]+=c;sa[i]+=c;so[i]+=c;
}
void down(int i)
{
if(!fl[i]) return ;
add(i<<1,fl[i]);add(i<<1|1,fl[i]);fl[i]=0;
}
void gand(int i,int l,int r,int L,int R,int x)
{
if(L>r || l>R) return ;
if(L<=l && r<=R && (sa[i]&(~x))==(so[i]&(~x)))
{add(i,(sa[i]&x)-sa[i]);return ;}
int mid=(l+r)>>1;down(i);
gand(i<<1,l,mid,L,R,x);
gand(i<<1|1,mid+1,r,L,R,x);
up(i);
}
void gor(int i,int l,int r,int L,int R,int x)
{
if(L>r || l>R) return ;
if(L<=l && r<=R && (sa[i]&x)==(so[i]&x))
{add(i,(sa[i]|x)-sa[i]);return ;}
int mid=(l+r)>>1;down(i);
gor(i<<1,l,mid,L,R,x);
gor(i<<1|1,mid+1,r,L,R,x);
up(i);
}
int ask(int i,int l,int r,int L,int R)
{
if(l>R || L>r) return -1e9;
if(L<=l && r<=R) return mx[i];
int mid=(l+r)>>1;down(i);
return max(ask(i<<1,l,mid,L,R),
ask(i<<1|1,mid+1,r,L,R));
}
signed main()
{
freopen("farm.in","r",stdin);
freopen("farm.out","w",stdout);
n=read();m=read();build(1,1,n);
for(int i=1;i<=m;i++)
{
int op=read(),l=read(),r=read();
if(op==1) gand(1,1,n,l,r,read());
if(op==2) gor(1,1,n,l,r,read());
if(op==3) write(ask(1,1,n,l,r)),puts("");
}
}