@5 UOJ75 & UOJ181 & UOJ187

智商锁

题目描述

点此看题

解法

没什么好的思路,观察 subtask5,发现每个数都可以被表示成 \(3,4,5,6...\) 的幂次,这提示了我们什么?

关键的突破口是:在两个独立的图之间架设一条桥边,得到新图的生成树个数是原来两个数的乘积

换句话说,我们只需要合理构造初始的图,使得它们组合出来的方案数取遍 \([0,998244353)\) 即可。但这样做还是不太现实,考虑随机化,我们随机 \(1000\)\(16\) 阶的图,以它们的生成树个数为基本量。

对于询问,我们找到一个四元组 \((a,b,c,d)\),使得 \(a\cdot b\cdot c\cdot d=k\bmod 998244353\),可以考虑折半搜索,也就是把所有 \((a,b)\) 塞进 map 里面,再用 \(k\cdot c^{-1}\cdot d^{-1}\) 去查找即可。

正确率如何计算呢?由于 \(16\) 阶图的生成树远大于模数,所以可以假设得到的 \(1000\) 个基本量在值域范围内均匀分布。大致地假设乘积也均匀分布,单次出错的概率是 \(p=(1-\frac{1}{998244353})^{10^{12}}\approx 0\)

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
const int M = 1005;
const int n = 16;
#define int long long
const int MOD = 998244353;
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 T,m,cnt[M],inv[M],e[M];
map<int,int> mp;
struct node
{
	int a[20][20];
	node() {memset(a,0,sizeof a);}
	int* operator [] (int x) {return a[x];}
}g[M];
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 Matrix(node &g)
{
	int ans=1,a[20][20]={};
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) if(g[i][j])
			a[i][j]=MOD-1,a[i][i]++;
	for(int i=2;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
			if(!a[i][i] && a[j][i])
			{
				swap(a[i],a[j]);
				ans=MOD-ans;break;
			}
		if(!a[i][i]) return 0;
		ans=ans*a[i][i]%MOD;
		for(int j=i+1;j<=n;j++)
		{
			if(!a[j][i]) continue;
			int t=a[j][i]*qkpow(a[i][i],MOD-2)%MOD;
			for(int k=i;k<=n;k++)
				a[j][k]=(a[j][k]-a[i][k]*t%MOD+MOD)%MOD;
		}
	}
	return ans;
}
void init()
{
	mt19937 z(114514);
	for(int t=1;t<=1000;t++)
	{
		node h;e[t]=0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<i;j++)
				e[t]+=(h[i][j]=h[j][i]=((z()&15)<=12));
		cnt[t]=Matrix(h);
		if(cnt[t]==0) {t--;continue;}
		g[t]=h;inv[t]=qkpow(cnt[t],MOD-2);
		for(int i=1;i<=t;i++)
			mp[cnt[i]*cnt[t]%MOD]=(i<<16)|t;
	}
}
void print(node &g,int t)
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++) if(g[i][j])
			printf("%d %d\n",i+t,j+t);
}
void work()
{
	m=read();
	if(!m) {puts("64 0");return ;}
	for(int i=1;i<=1000;i++)
		for(int j=1;j<=i;j++)
		{
			int x=inv[i]*inv[j]%MOD;
			if(!x) continue;x=x*m%MOD;
			if(mp.find(x)==mp.end()) continue;
			int a=mp[x]>>16,b=mp[x]&65535;
			printf("64 %d\n",e[i]+e[j]+e[a]+e[b]+3);
			print(g[i],0);print(g[j],n);
			print(g[a],n*2);print(g[b],n*3);
			printf("%d %d\n",n,n+1);
			printf("%d %d\n",n*2,n*2+1);
			printf("%d %d\n",n*3,n*3+1);
			return ;
		}
}
signed main()
{
	init();
	T=read();
	while(T--) work();
}

密码锁

题目描述

点此看题

解法

考虑贡献法,因为竞赛图强连通缩点之后会得到一条链,前面的点会向后面的点连接单向边。所以我们枚举一个点集 \(S\),强制它向它的补集连单向边,并且贡献 \(1\) 的强连通分量。

但是枚举点集是不现实的,我们要把复杂度和边数挂钩。于是考虑特殊边的每个连通块,每个连通块内枚举点集,计算概率。不同连通块之间是相对独立的,它们之间只有概率为 \(\frac{1}{2}\) 的边,所以只需要知道 \(S\) 的大小即可。

那么拿个背包记录点集的大小即可,时间复杂度 \(O(n\cdot 2^n)\)

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 70;
const int MOD = 998244353;
#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,m,ans,inv,f[M],g[M],fa[M],a[M],b[M],w[M];
vector<int> v[M];
int find(int x)
{
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
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;
}
signed main()
{
	n=read();m=read();inv=qkpow(10000,MOD-2);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		a[i]=read(),b[i]=read(),w[i]=read();
		fa[find(a[i])]=find(b[i]);
	}
	for(int i=1;i<=n;i++)
		v[find(i)].push_back(i);
	f[0]=1;
	for(int o=1;o<=n;o++) if(find(o)==o)
	{
		vector<int> h=v[o];int len=h.size();
		memcpy(g,f,sizeof f);
		memset(f,0,sizeof f);
		for(int i=0;i<(1<<len);i++)
		{
			int s1=0,s2=0,cnt=0,c=1;
			for(int j=0;j<len;j++)
			{
				if(i>>j&1) cnt++,s1|=1ll<<h[j];
				else s2|=1ll<<h[j];
			}
			for(int j=1;j<=m;j++)
				if((s1>>a[j]&1)!=(s1>>b[j]&1))
				{
					c=c*((s1>>b[j]&1)?w[j]:10000-w[j])
					%MOD*inv%MOD*2%MOD;
				}
			for(int j=n;j>=0;j--) if(g[j])
				f[j+cnt]=(f[j+cnt]+g[j]*c)%MOD;
		}
	}
	for(int i=0;i<n;i++)// i=0 & i=n are the same
		ans=(ans+f[i]*qkpow((MOD+1)/2,i*(n-i)))%MOD;
	ans=ans*qkpow(10000,n*(n-1))%MOD;
	printf("%lld\n",ans);
}

Ernd

题目描述

点此看题

解法

考虑从 \(j\) 出发可以接到 \(i\) 的条件是:\(|a_{i}-a_j|\leq b_i-b_j\),如果按 \(b_i\) 的顺序 \(dp\),在拼接连续接到的两段时无法避免这个条件的处理,于是需要树套树解决。换一种想法,把点 \(i\) 看成二维平面上的坐标 \((b_i,a_i)\),那么可以接到的范围被两条斜率为 \(\pm 1\) 的射线包围住。

所以把坐标系 \(45\)° 旋转,令 \((x_i,y_i)=(b_i-a_i,b_i+a_i)\),那么条件变为 \(x_j\leq x_i,y_j\leq y_i\),记为 \(j\prec i\)

现在有了这个二维偏序,考虑按照 \(x_i\) 排序(注意只是按这东西排序,编号还是原序列的编号),设 \(f_i\) 表示 \(i\) 作为连续段的结尾点时的最大价值,转移:

  • 新增一个连续段的起点:\(f_i=\max_{j\prec i} f_j+1\)
  • 新增一个连续段的结尾:\(f_i=\max_{j<i} f_j+(i-j+1)^2-1\),要求 \(i,j\) 在原序列中的一个极长连续段中。

第一种转移现在是好解决的,因为已经按 \(x_i\) 排过序,所以按照 \(y_i\) 建立树状数组就可以了。

第二种转移显然是凸包的形式,我们不妨先忽略后面的那个限制来推一下式子,考虑 \(j<k\)\(k\)\(j\) 优的条件:

\[f_j+(i-j+1)^2-1<f_{k}+(i-k-1)^2-1 \]

\[f_j+j^2-2j(i+1)<f_{k}+k^2-2k(i+1) \]

\[2(i+1)<\frac{(f_k+k^2)-(f_j+j^2)}{k-j} \]

\(s_i=f_i+i^2\),因为条件是斜率要大于某个值,所以我们维护下凸包。因为 \(2(i+1)\) 是单增的,所以我们总是弹出新加入的节点,那么我们用的形式维护这个凸包。

再把这个限制考虑上,即在原序列上 \(a_j\prec a_{j+1}...\prec a_i\),由于原序列的极长连续段不交,且排序后的相对位置不变,所以只会涉及到凸包的插入,并不会涉及到合并与分裂。那么我们同时维护若干个凸包即可(代表原序列若干个极长连续段的转移),可以用链表的思想记录上一个点是谁,实现起来就比较方便了,时间复杂度 \(O(n\log n)\)

注意:不能连续进行两次第二种转移,所以在第一个转移之后就需要固定当前点的 \(f_i\) 了。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 500005;
#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,m,ans,p[M],b[M],d[M],l[M],f[M],y[M];
struct node
{
	int x,y;
	bool operator < (const node &b) const
		{return x!=b.x?x<b.x:y<b.y;}
	bool operator << (const node &b) const
		{return x<=b.x && y<=b.y;}
}a[M];
void add(int x,int c)
{
	for(int i=x;i<=m;i+=i&(-i))
		b[i]=max(b[i],c);
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=i&(-i))
		r=max(r,b[i]);
	return r;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int u=read(),v=read();
		a[i].x=v-u;p[i]=i;
		d[i]=a[i].y=v+u;
	}
	sort(p+1,p+1+n,[&](int x,int y)
	{return a[x]<a[y];});
	sort(d+1,d+1+n);
	m=unique(d+1,d+1+n)-d-1;
	for(int i=1;i<=n;i++)
		a[i].y=lower_bound(d+1,d+1+m,a[i].y)-d;
	for(int o=1;o<=n;o++)
	{
		int i=p[o],j=i-1;
		f[i]=ask(a[i].y)+1;y[i]=f[i]+i*i;
		if(i>1 && a[i-1]<<a[i])
		{
			for(;l[j] && (y[j]-y[l[j]])
			<=2*(i+1)*(j-l[j]);j=l[j]);
			f[i]=max(f[i],y[j]-2*(i+1)*j+i*(i+2));
			//
			for(;l[j] && (y[i]-y[j])*(j-l[j])
			>=(y[j]-y[l[j]])*(i-j);j=l[j]);
			l[i]=j;
		}
		add(a[i].y,f[i]);
		ans=max(ans,f[i]);
	}
	printf("%lld\n",ans);
}
posted @ 2022-07-14 16:04  C202044zxy  阅读(101)  评论(0编辑  收藏  举报