WC2021题目略解

T1

solution

不会?完全没思路?那就来看看性质吧

如果\(x\to y\) 合法,那么\(y\to x\) 合法(自反性)

如果\(x\to y,y\to z\) 合法,那么\(x\to z\) 合法(传递性)

由此可知若将可以通过合法序列到达的点连边,形成的图一定是一个由若干团拼成的,而我们只需要找出这些团最后统计答案即可

考虑合法的括号序列是如何生成的?

  • 空串合法
  • \(A\)合法\(\to\) \((A)\)合法
  • \(A,B\)合法\(\to\) \(AB\)合法

由此我们可以以\(()\)为基础,通过和合法串的拼接或在外面加括号实现任意合法括号序列的生成

\(()\)如何出现?

如图,两条黑边的括号类型是相同的,此时\(x\to y(y\to x)\)就是\(()\)

考虑合法串拼接,即\(x\to y\to z\) (其中\(y\to z\) 合法)

考虑在外面加括号,即\(a\to x\to y\to b\) (其中\(a\to x,b\to y\) 的边都是同类型的左括号)

在这两种生成方式中,不难发现和具体\(x\)如何到\(y\)没有关系,只关心是否可达

意即可以将\(x,y\)合并当作一个点处理

根据上面的思考不难看出\(x,y\)的合并可能会引出新的合并,而我们只需不断地这样合并直到没有合法点对,此时算法结束,所有点所在的团的编号已被求出

具体实现可以采用哈希表映射存边(括号类型是原象,边起点是象),用并查集模拟合并,用队列处理一次合并引发出的多次合并。另外,为保证复杂度,需使用启发式合并。

time complexity

\(\mathcal O(m\log m)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
typedef pair<int,int> pii;
typedef long long ll;
unordered_map<int,int>mp[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
int fa[N],sz[N],n,m,k,cnt[N];
int fd(int x){return fa[x]==x?x:fa[x]=fd(fa[x]);}
queue<pii>q;
inline void mge(int x,int y)
{
	x=fd(x),y=fd(y);
	if(x==y)return;
	if(sz[x]<sz[y])swap(x,y);fa[y]=x;
	for(auto t=mp[y].begin();t!=mp[y].end();t++)
	{
		if(!mp[x].count(t->first))mp[x][t->first]=t->second,++sz[x];
		else q.push(make_pair(t->second,mp[x][t->first]));
	}
}
int main()
{
	n=read(),m=read(),k=read();
	for(int i=1,u,v,w;i<=m;++i)
	{
		u=read(),v=read(),w=read();
		if(!mp[v].count(w))mp[v][w]=u,++sz[v];
		else q.push(make_pair(mp[v][w],u));
	}
	for(int i=1;i<=n;++i)fa[i]=i;
	while(!q.empty())
	{
		auto t=q.front();q.pop();
		mge(t.first,t.second);
	}ll ans=0;
	for(int i=1;i<=n;++i)++cnt[fd(i)];
	for(int i=1;i<=n;++i)ans+=1ll*cnt[i]*(cnt[i]-1)/2;
	printf("%lld\n",ans);
	return 0;
}

T2

solution

对于表达式\(E\) ,不妨建出其表达式树(为二叉树),它的特点如下

  • 叶子节点表示操作数
  • 非叶子节点代表一次运算,即对左右儿子进行一次\(>\)\(<\) 运算

设其根为\(rt\)

不难发现数组的每一位都是相对独立的,因此只用考虑\(m\)个数的情况,设它们为\(a_1,a_2,\cdots ,a_m\)

考虑到\(m\)很小,同时最终答案也必然在\(m\)个数中产生,我们不妨考虑每个数作为答案出现多少次,然后加和

直接做不好做,但是我们发现,当枚举到\(i\) 时,\(a_i\) 成为最终答案的次数仅仅和\(a_1,a_2,\cdots,a_m\) 中哪些比它大,哪些比它小,哪些和它相等有关,而和具体相差多少无关。于是我们可以把其中比它大的看作\(2\), 和它相等的看作\(1\), 比它小的看作\(0\), 然后我们就可以在表达式树上\(dp\)

\(f_{u,k}\)表示节点\(u\)的计算结果为\(k\)的方案数,考虑转移

  • \(u\)是叶子节点时,将对应的状态置为\(1\)即可
  • \(u\)是非叶子节点时,设其左儿子为\(l\),右儿子为\(r\)

如果\(u\)对应的操作符为\(>\),那么

\[f_{u,k}=\sum_{\max(i,j)=k}f_{l,i}\times f_{r,j} \]

若其操作符为\(<\),那么

\[f_{u,k}=\sum_{\min(i,j)=k}f_{l,i}\times f_{r,j} \]

若为\(?\),考虑加法原理,只需把以上两式相加即可

最终答案就是\(f_{rt,1}\)

时间复杂度\(\mathcal O(nm(m+|E|))\)

这样下来复杂度仍然无法承受,考虑优化

能否不枚举\(i\)?答案是肯定的,因为每一次都是\(m\)个数,而每个数都在\(0\)\(2\)之间,于是我们可以先预处理出所有情况,然后记下答案。每次先将\(a\)数组排序,从小到大地考虑即可

时间复杂度\(\mathcal O(3^m|E|+n(m\log_2m+m))\)

然而预处理的复杂度仍然难以承受,考虑继续优化

可以将小于\(a_i\)的看作\(0\),将大于等于\(a_i\)的看作\(1\), 这样可以求出结果大于等于\(a_i\)的方案数,然后再差分一下就可以得到答案了

时间复杂度\(\mathcal O(2^m|E|+n(m\log_2m+m))\)

可以承受

code

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=5e4+5,K=12,mod=1e9+7;
int n,m,A[K][N],rt,tot,lb[N],pos[N],sta[N],top;
int ls[N],rs[N],ans,d[K];pii B[K]; 
char s[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
void build(int&p,int l,int r)
{
	if(pos[r]==l)return build(p,l+1,r-1);
	p=++tot;int t=0;
	if(l==r){lb[p]=s[l]^48;return;}
	for(int i=r;i>=l;--i)
	{
		if(pos[i]){i=pos[i];continue;}
		if(s[i]=='<'||s[i]=='>'||s[i]=='?'){t=i;break;}
	}lb[p]=s[t];
	build(ls[p],l,t-1),build(rs[p],t+1,r);
}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int f[N][2],ret[1200];
inline void upmn(int u)
{
	for(int i=0;i<=1;++i)
		for(int j=0;j<=1;++j)
		{
			int&d=f[u][min(i,j)];
			d=add(d,1ll*f[ls[u]][i]*f[rs[u]][j]%mod);
		}
}
inline void upmx(int u)
{
	for(int i=0;i<=1;++i)
		for(int j=0;j<=1;++j)
		{
			int&d=f[u][max(i,j)];
			d=add(d,1ll*f[ls[u]][i]*f[rs[u]][j]%mod);
		}
}
void dp(int u)
{
	f[u][0]=f[u][1]=0;
	if(!ls[u]){f[u][d[lb[u]]]=1;return;}
	dp(ls[u]),dp(rs[u]);
	if(lb[u]!='<')upmx(u);
	if(lb[u]!='>')upmn(u);
}
inline int gans(int w)
{
	if(ret[w]>=0)return ret[w];
	for(int i=0;i<m;++i)d[i]=(w>>i)&1;
	dp(rt);return ret[w]=f[rt][1];
}
int main()
{
	n=read(),m=read();
	for(int i=0;i<m;++i)
		for(int j=1;j<=n;++j)
			A[i][j]=read();
	scanf("%s",s+1);
	int len=strlen(s+1);
	for(int i=1;i<=len;++i)
	{
		if(s[i]=='(')sta[++top]=i;
		else if(s[i]==')')pos[i]=sta[top--];
	}
	build(rt,1,len);
	int st=1<<m;fill(ret,ret+st,-1);
	#define x first
	#define y second
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<m;++j)
			B[j]=make_pair(A[j][i],j);
		sort(B,B+m);
		int p=st-1,pre=gans(p);
		for(int j=0;j<m;)
		{
			int val=B[j].x;
			p^=1<<B[j].y,++j;
			while(B[j].x==B[j-1].x)p^=1<<B[j].y,++j;
			int now=gans(p);
			ans=add(ans,1ll*val*dec(pre,now)%mod);
			pre=now;
		}
	}
	#undef x
	#undef y
	printf("%d\n",ans);
	return 0;
}

T3

solution

先定义数列\(f\)

\[f_0=1,f_1=0\\ f_n=f_{n-1}+f_{n-2}(n\ge 2) \]

然后有结论:数列\(f\)在模\(m\)意义下是纯循环的,且最小正周期是\(\mathcal O(m)\)的(常数也不大)

证明?自己写程序吧

不难证明原题中的\(F\)可以表示为

\[F_n=af_{n}+bf_{n+1}(n\ge 0) \]

依题意,即要求最小的\(n\)满足(\(a\)\(b\)等于\(0\)的情况先特判掉)

\[af_n+bf_{n+1}\equiv 0\pmod m \]

\[af_n\equiv (m-b)f_{n+1}\pmod m\\ -\frac ab\equiv \frac {f_{n+1}}{f_{n}}\pmod m \]

看上去问题似乎已经解决了,只需预处理所有的\(\frac {f_{n+1}}{f_n}\bmod m\),然后询问时查表即可

然而注意到\(b,f_n\)可能和\(m\)并不互质,这导致其在模\(m\)意义下并不存在逆元。怎么办?

引理:

\[ad\equiv bd\pmod {cd}\Leftrightarrow a\equiv b\pmod c \]

不妨令\(g=\gcd(a,m-b,m),a'=\frac ag,b'=\frac {m-b}g,m'=\frac mg\),由此

\[a'f_n\equiv b'f_{n+1}\pmod {m'}\\ a'f_n=b'f_{n+1}+km'(k\in \Z) \]

考察如上文氏图

相交的部分代表公共的因子

由于

\[p\mid a',p\mid m' \]

所以

\[p\mid b'f_{n+1}\\ \Rightarrow p\mid f_{n+1} \]

同理得

\[q\mid f_n \]

由于

\[f_n\perp f_{n+1} \]

(辗转相除求最大公约数的方法可以证明此结论)

因此

\[p\nmid f_n,q\nmid f_{n+1} \]

而倘若\(m''\mid f_n\),则\(m''\mid f_{n+1}\),与此二者互质矛盾,反之可得相同结论,因此

\[m''\nmid f_n,m''\nmid f_{n+1} \]

所以

\[\gcd(a',m')=\gcd(f_{n+1},m')=p\\ \gcd(b',m')=\gcd(f_n,m')=q \]

因此可得

\[\frac {a'}p\cdot \frac {f_n}q\equiv \frac {b'}q\cdot\frac{f_{n+1}}p\pmod {\frac {m'}{pq}}\\ \frac{\frac{a'}p}{\frac{b'}q}\equiv \frac{\frac{f_{n+1}}p}{\frac{f_n}q}\pmod {\frac {m'}{pq}} \]

此时的除法显然是有意义的

因此,我们只需采用\(map\) 映射,将四元组\((m',p,q,\frac{\frac{f_{n+1}}p}{\frac{f_n}q}\bmod \frac{m'}{pq})\) 映射到对应的最小的\(n\)即可

预处理时枚举\(m'\) (即\(m\) 的约数),然后花费\(\mathcal O(m')\) 的时间处理出\(m'\) 处的上述四元组

询问时按照上述过程运算,然后查询即可

time complexity

预处理复杂度:\(\mathcal O(\sigma(m)\log_2(\sigma(m)))=\mathcal O(m\ln m(\ln m+\ln\ln m))\) 其中\(\sigma(m)\) 为因数和

查询复杂度:\(\mathcal O(n\log m)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
struct node{int p,q,k;};
inline bool operator<(const node&x,const node&y)
{
	return x.p!=y.p?x.p<y.p:(x.q!=y.q?x.q<y.q:x.k<y.k);
}
map<node,int>mp[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void exgcd(int&x,int&y,int a,int b)
{
	if(!b){x=1,y=0;return;}
	exgcd(x,y,b,a%b);
	int t=x;x=y,y=t-a/b*y;
}
inline int inv(int a,int mod)
{
	int x,y;exgcd(x,y,a,mod);
	return (x%mod+mod)%mod;
}
inline void pre()
{
	for(int _=2;_<=m;++_)
	{
		if(m%_)continue;
		int x=1,y=0;
		for(int cnt=0;;++cnt)
		{
			if(x&&y)
			{
				int d1=gcd(_,x),d2=gcd(_,y),pm=_/d1/d2;
				int k=1ll*(y/d2)*inv(x/d1,pm)%pm;
				if(!mp[_].count({d1,d2,k}))mp[_][{d1,d2,k}]=cnt;
			}
			int t=x;x=y,y=(y+t)%_;
			if(x==1&&y==0)break;
		}
	}
}
int main()
{
	n=read(),m=read();pre();
	while(n--)
	{
		int a=read(),b=read();
		if(!a){puts("0");continue;}
		if(!b){puts("1");continue;}b=(m-b)%m;
		int g=gcd(gcd(a,b),m),m1=m/g;a/=g,b/=g;
		int p=gcd(a,m1),q=gcd(b,m1),m2=m1/p/q;
		a/=p,b/=q;
		int k=1ll*a*inv(b,m2)%m2;
		if(!mp[m1].count({q,p,k}))puts("-1");
		else printf("%d\n",mp[m1][{q,p,k}]);
	}
	return 0;
}
posted @ 2021-02-11 20:05  BILL666  阅读(125)  评论(0编辑  收藏  举报