【日常摸鱼】WC2021
【日常摸鱼】WC2021
前言
难就完事了
括号路径
链接
题意
给定一张\(n\)个点\(2m\)条边的有向图,图中的每条边上都有一个标记,代表一个左括号或者右括号。共有\(k\)种不同的括号类型,即图中可能有\(2k\)种不同的标记。点、边、括号种类均从\(1\)开始编号。
图中的每条边都会和另一条边成对出现。更具体地,若图中存在一条标有第\(w\)种括号的左括号的边\((u,v)\),则图中一定存在一条标有第\(w\)种括号的右括号的边\((v,u)\)。同样地,图中每条标有右括号的边将对应着一条反方向的标有同类型左括号的边。
现在请你求出,图中共有多少个点对\((x,y)(1≤x<y≤n)\)满足:图中存在一条从\(x\)出发到达\(y\)的路径,且按经过顺序将路径各条边上的标记拼接得到的字符串是一个合法的括号序列。。
\(n\leq 300000,m\leq 600000,w \leq k\leq n\)
题解
这个题我一开始连部分分都写不来QAQ根本没思路。。。
题解太神了!!!
1.因为边是成对出现的,如果\(x->y\)是合法路径,那么\(y->x\)也是合法路径。
2.因为如果\(x->y\)和\(y->z\)都是合法路径,\(x->z\)也是合法路径。。
于是就能发现能相互到达的点形成的是若干个团!
于是我们只要求出每个团的大小,团内的所有点都是能相互到达的。
然后就是合并的问题了,我们发现如果一个团内的某个点连出的相同的右括号边指向的两点是可以合并的。
于是我们不停的依照这种方法合并,用队列和并查集维护一下就行。。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int n,m,K;
map<int,int> mp[N];
queue< pair<int,int> > q;
int fa[N];
LL cnt[N];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void insert(int u,int v){
u=find(u);v=find(v);
if(u==v) return;
if(cnt[u]>cnt[v]) swap(u,v);
int x,y;
for(map<int,int>::iterator it=mp[u].begin();it!=mp[u].end();++it){
x=it->first;y=it->second;
if(mp[v][x]) q.push(make_pair(mp[v][x],y));
else mp[v][x]=y;
}
cnt[v]+=cnt[u];
fa[u]=v;
}
int main(){
int u,v,w;
pair<int,int> x;
n=read();m=read();K=read();
for(int i=1;i<=n;++i) {
fa[i]=i;cnt[i]=1;
}
for(int i=1;i<=m;++i){
u=read();v=read();w=read();
if(mp[v][w]) q.push(make_pair(mp[v][w],u));
else mp[v][w]=u;
}
while(!q.empty()){
x=q.front();q.pop();
insert(x.first,x.second);
}
LL ans=0;
for(int i=1;i<=n;++i) if(find(i)==i) ans+=(cnt[i]-1)*cnt[i]/(LL)2;
printf("%lld\n",ans);
return 0;
}
表达式求值
链接
题意
定义二元操作符\(<\):对于两个长度都为\(n\)的数组\(A,B\)(下标从 \(1\) 到 \(n\)),\(A<B\) 的结果也是一个长度为\(n\)的数组,记为\(C\)。则有\(C[i]=min(A[i],B[i])(1≤i≤n)\)。
定义二元操作符\(>\):对于两个长度都为\(n\)的数组\(A,B\)(下标从 1 到 n),\(A>B\) 的结果也是一个长度为\(n\)的数组,记为\(C\)。则有\(C[i]=max(A[i],B[i])(1≤i≤n)\)。
现在有\(m(1≤m≤10)\)个长度均为\(n\)的整数数组 \(A0,A1,…,Am−1\)。给定一个待计算的表达式 \(E\),其满足 \(E\) 中出现的每个操作数都是 \(A0,A1,…,Am−1\) 其中之一,且 \(E\) 中只包含 \(<\) 和 \(>\) 两种操作符(\(<\) 和 \(>\) 的运算优先级相同),因此该表达式的结果值也将是一个长度为 \(n\) 的数组。
特殊地,表达式 \(E\) 中还可能出现操作符 \(?\),它表示该运算符可能是 \(<\) 也可能是 \(>\)。因此若表达式中有 \(t\) 个 \(?\),则该表达式可生成 \(2t\) 个可求确定值的表达式,从而可以得到 \(2t\) 个结果值,你的任务就是求出这 \(2t\) 个结果值(每个结果都是一个数组)中所有的元素的和。你只需要给出所有元素之和对 \({10}^9+7\) 取模后的值。
\(n\leq 50000,m\leq 10 ,表达式长度\leq 50000,a_i \leq {10}^9\)
题解
这套WC里唯一一个我会的题QAQ
如果不是之前做过类似思路的题,估计我就一点都不会了。。
首先肯定是对表达式做一下表达式树。。(表达式一般只有后缀表达式和表达式树两条路)
然后对于每一列是独立的,可以分开做。
然后我们就是考虑对于一列怎么做,显然我们只用算每个数最终出现了几次。
由于这个表达式树只有min和max两种操作,那么一个数\(a_i\)出现的次数之和\(a_1,a_2,...,a_m\)中有哪几个数大于它,哪几个数不比它小就好。
为了方便比较,值相同的数可以比较位置。。
我们定义\(f(S,x,0/1)\)表示比它小的数的状态表示是S,在表达式树节点x上,最终值小于/大于询问值的方案数。
这个东西就能轻易的DP了,DP式比较明显而且很长,就不细说了。
因为\(m\leq 10\),\(S\)直接用二进制表示即可。
对于某一个位置的数a_{i,j}(第i列第j行),答案即为\(f(S,root,1)-f(S|(2^{i}),root,1)\)
效率是\(O(|E|2^m+nm^2)\),时限1秒有一点点卡常。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=1e9+7;
const int N=5e4+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n,m,rt,cnt,tp;
LL a[11][N];
char s[N];
struct Node{
int ls,rs;
int num;
}d[N<<1];
char qq[N<<1];
int q[N<<1];
LL ans[(1<<10)+10];
LL f[N][2];
void Merge(){
while((qq[tp-1]=='>'||qq[tp-1]=='<'||qq[tp-1]=='?')&&tp>=3){
++cnt;
d[cnt].ls=q[tp-2];
d[cnt].rs=q[tp];
if(qq[tp-1]=='>')d[cnt].num=1;
else if(qq[tp-1]=='<')d[cnt].num=2;
else d[cnt].num=0;
qq[tp-2]='*';
q[tp-2]=cnt;
tp-=2;
}
return;
}
void Get_Tree(){
int len=strlen(s+1),x;tp=0;cnt=0;
for(int i=1;i<=len;++i){
qq[++tp]=s[i];
if(s[i]>='0'&&s[i]<='9') {
q[tp]=++cnt;
d[cnt].ls=d[cnt].rs=0;
d[cnt].num=s[i]-'0';
Merge();
}
else if(s[i]==')'){
q[tp-2]=q[tp-1];tp-=2;
Merge();
}
}
rt=q[tp];
return;
}
void DP(int x,int S){
f[x][0]=f[x][1]=0;
if(d[x].ls==0&&d[x].rs==0){
if(S&(1<<d[x].num)) f[x][0]=1;
else f[x][1]=1;
return;
}
DP(d[x].ls,S);DP(d[x].rs,S);
int L=d[x].ls,R=d[x].rs;
if(d[x].num==1){
f[x][0]=f[L][0]*f[R][0];
f[x][1]=(f[L][0]+f[L][1])*(f[R][0]+f[R][1])-f[L][0]*f[R][0];
if(f[x][1]>=P)f[x][1]%=P;
if(f[x][0]>=P)f[x][0]%=P;
}
else if(d[x].num==2){
f[x][1]=f[L][1]*f[R][1];
f[x][0]=(f[L][0]+f[L][1])*(f[R][0]+f[R][1])-f[L][1]*f[R][1];
if(f[x][1]>=P)f[x][1]%=P;
if(f[x][0]>=P)f[x][0]%=P;
}
else{
LL V=(f[L][0]+f[L][1])*(f[R][0]+f[R][1]);
f[x][0]=V-f[L][1]*f[R][1]+f[L][0]*f[R][0];
f[x][1]=V-f[L][0]*f[R][0]+f[L][1]*f[R][1];
if(f[x][1]>=P)f[x][1]%=P;
if(f[x][0]>=P)f[x][0]%=P;
}
return;
}
void MAIN(){
scanf("%d%d",&n,&m);
for(int i=0;i<m;++i)
for(int j=1;j<=n;++j)
a[i][j]=read();
scanf("%s",s+1);
Get_Tree();
for(int i=0;i<(1<<m);++i){
DP(rt,i);
ans[i]=f[rt][1];
}
int S;
LL res=0,T;
for(int j=1;j<=n;++j){
for(int i=0;i<m;++i){
S=0;
for(int k=0;k<m;++k){
if(a[i][j]>a[k][j]||(a[i][j]==a[k][j]&&k<i)){
S|=(1<<k);
}
}
T=ans[S];
pls(T,P-ans[(S|(1<<i))]);
pls(res,a[i][j]*T%P);
}
}
printf("%lld\n",res);
return;
}
int main(){
int ttt=1;
while(ttt--) MAIN();
return 0;
}
斐波那契
链接
题意
我们定义 \(F_0=a,F_1=b,F_i=(F_{i−1}+F_{i−2})modm(i≥2)\)。
现在给定 \(n\) 组询问,对于每组询问请找到一个最小的整数 \(p\),使得 \(F_p=0\)。
\(1\leq n,m\leq 100000\),\(0\leq a,b\leq m\)
题解
思路太奇妙了。。。。我肯定是做不出这么难的题的。。。
补题的时候看到一个极其优美的做法。。https://www.luogu.com.cn/blog/tiw-air-oao/solution-p7325
首先当\(n>0\)时,\(F_i=afib_{i-1}+bfib_{i}\)
于是要搞的式子\(afib_{n-1}+bfib_{n}=0 mod m\)
然后\(a,b,m\)同时除\((a,b,m)\)等式仍成立。
得到\(a'fib_{n-1}+b'fib_{n}=0 mod m'\)
由于\((fib_{n-1},fib_{n})=1\),于是应有\((a',m')=(fib_{n},m')=p\)和\((b',m')=(fib_{n-1},m')=q\)
等式两边同除pq就能得到互质的式子。
然后只用hash一下三元组\(<p,q,\frac{fib_{n}}{fib_{n-1}} mod \frac{m'}{pq}>\)就可以了
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
#define ctz __builtin_ctz
int gcd(int a,int b){
if(!a||!b)return a|b;
int t=ctz(a|b);
a>>=ctz(a);
do{
b>>=ctz(b);
if(a>b)swap(a,b);
b-=a;
}while(b);
return a<<t;
}
void exgcd(int a,int b,int &x,int &y){
if(!b){x=1;y=0;return;}
exgcd(b,a%b,y,x);
y-=a/b*x;
return;
}
int ny(int a,int m){
int x,y;exgcd(a,m,x,y);
x=x%m;
if(x<0)x+=m;
return x;
}
const LL sed=1e5+1;
LL getnum(int x,int y,int z){
return (LL)x*sed*sed+(LL)y*sed+(LL)z;
}
unordered_map<LL,int> mp[100005];
int n,m;
void init(){
LL tmp;
int x,y,z,d1,d2;
for(int i=2;i<=m;++i) {
if(m%i==0){
x=1;y=0;d1=1;d2=m;
for(int j=0;;++j){
if(x&&y){
int m1=i/d1/d2;
int k=(LL)(y/d2)*ny((x/d1),m1)%m1;
tmp=getnum(k,d1,d2);
if(!mp[i].count(tmp)) mp[i][tmp]=j;
}
z=x+y;
if(z>=i)z-=i;
x=y;y=z;
swap(d1,d2);
d2=gcd(i,y);
if(x==1&&y==0) break;
}
}
}
}
void MAIN(){
n=read();m=read();
init();
int a,b;
for(int i=1;i<=n;++i){
a=read();b=read();
b=(m-b)%m;
if(a==0){puts("0");continue;}
if(b==0){puts("1");continue;}
int p=gcd(a,m),q=gcd(b,m);
int d=gcd(p,q),m1=m/d;a=a/p;b=b/q;
p=p/d;q=q/d;
int m2=m1/p/q;
int k=(LL)a*ny(b,m2)%m2;
int res=mp[m1][getnum(k,q,p)];
if(res) printf("%d\n",res);
else puts("-1");
}
return;
}
int main(){
int ttt=1;
while(ttt--) MAIN();
return 0;
}