UOJ Round #5 题解

A

构造的试卷每一题都是 A 0 0 0 0,这样方案数为 3 n − 1 × 4 3^{n-1}\times 4 3n1×4

证明的话参考官方题解吧,我这只是手玩出来之后觉得大概没有更优解于是就写了。

代码大概没人需要吧……还是要稍微放一下的

#include <cstdio>

int n;

int main()
{
	scanf("%d",&n);
	int ans=4;for(int i=2;i<=n;i++)ans=3ll*ans%998244353;
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)printf("A 0 0 0 0\n");
}

B

不难发现肯定要先按权值将所有修路操作排序,然后每次操作要将尽可能多的连通块连在一起。

考虑一次修路,假设限制个数为 p i p_i pi,若 d i s ( u i , v i ) > p i dis(u_i,v_i)>p_i dis(ui,vi)>pi,那么这路上所有连通块都必定可以连在一起,用一个并查集来维护自己所在连通块中深度最小的节点即可,每次暴力往上跳。这部分的总复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)。(注意这里是不能按秩合并的,所以不是 O ( n α n ) O(n\alpha_n) O(nαn)。)

d i s ( u i , v i ) ≤ p i dis(u_i,v_i)\leq p_i dis(ui,vi)pi 时,考虑将所有限制连边,然后找到度数最小的点 x x x,将不跟他相连的点与他合并,这部分要用另外一个并查集来维护,并且上面那个部分合并连通块时,也要顺便维护这个并查集。

然后再暴力枚举与 x x x 相连的点,将他们之间进行合并,由于 x x x 选取了度数最小的点,所以这部分复杂度不超过 O ( p ) O(p) O(p)

最后再看看与 x x x 相连的点是否能跟不与 x x x 相连的点进行合并,这个也可以在 O ( p ) O(p) O(p) 内判断。

于是时间复杂度大概就是 O ( ( n + p ) log ⁡ n ) O((n+p)\log n) O((n+p)logn),代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 300010
#define pb push_back

int n,m,k,fa[maxn],dep[maxn];
struct edge{int x,y,z,t;}d[maxn];
bool cmp(edge x,edge y){return x.z<y.z;}
vector<edge> con[maxn];
bool check(int x,int y,int p){
	while(p--){
		if(dep[x]>dep[y])swap(x,y);
		y=fa[y];if(x==y)return false;
	}
	return true;
}
struct dsu{
	int fa[maxn];
	int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}
	dsu(){for(int i=1;i<=maxn-10;i++)fa[i]=i;}
}A,B;
long long ans=0;
void link(int x,int y,int z){
	int px=x,py=y;
	x=B.findfa(x);y=B.findfa(y);
	if(x!=y)B.fa[y]=x,ans+=z;//,printf("link : %d %d  %d\n",px,py,z);
}
vector<int> e[maxn];
edge sta[maxn];int top=0;
void add_edge(int x,int y){e[x].pb(y);e[y].pb(x);sta[++top]=(edge){x,y,0,0};}
void clear_edge(int x=0,int y=0){while(top)x=sta[top].x,y=sta[top--].y,e[x].clear(),e[y].clear();}
int S[maxn],sn;
void get_chain(int x,int y){
	sn=0;
	bool stop;do{
		stop=(x==y);
		if(dep[x]>dep[y])swap(x,y);
		S[++sn]=y;y=fa[y];
	}while(!stop);
}
bool v[maxn];

int main()
{
	scanf("%d %d %d",&n,&m,&k);
	for(int i=2;i<=n;i++)scanf("%d",&fa[i]),dep[i]=dep[fa[i]]+1;
	for(int i=1;i<=m;i++)scanf("%d %d %d",&d[i].x,&d[i].y,&d[i].z),d[i].t=i;
	for(int i=1,x,y,t;i<=k;i++){
		scanf("%d %d %d",&t,&x,&y);
		con[t].pb((edge){x,y,0,0});
	}
	sort(d+1,d+m+1,cmp);
	for(int i=1;i<=m;i++){
		int x=d[i].x,y=d[i].y,z=d[i].z,t=d[i].t;
		if(check(x,y,con[t].size())){
			x=A.findfa(x);y=A.findfa(y);
			while(x!=y){
				if(dep[x]>dep[y])swap(x,y);
				link(y,fa[y],z);A.fa[y]=fa[y];
				y=A.findfa(y);
			}
		}else{
			for(edge i:con[t])add_edge(i.x,i.y);
			get_chain(x,y);int mi=S[1];
			for(int j=2;j<=sn;j++)if(e[S[j]].size()<e[mi].size())mi=S[j];
			
			for(int j:e[mi])v[j]=true;
			for(int j=1;j<=sn;j++)if(!v[S[j]])link(mi,S[j],z);
			for(int j:e[mi]){
				int tot=0;for(int p:e[j])tot+=v[p];
				if(e[j].size()-tot-1<sn-e[mi].size()-1)link(j,mi,z);
			}
			for(int j:e[mi])v[j]=false;
			for(int j:e[mi]){
				for(int p:e[j])v[p]=true;
				for(int p:e[mi])if(!v[p])link(j,p,z);
				for(int p:e[j])v[p]=false;
			}
			clear_edge();
		}
	}
	printf("%lld",ans);
}

C

膜一膜神仙就会了。

如果你对下面的莫反有什么疑问的话,可以看看这个

首先大力推推柿子,显然有: l c m ( i , j ) = i j gcd ⁡ ( i , j ) lcm(i,j)=\frac {ij} {\gcd(i,j)} lcm(i,j)=gcd(i,j)ij,代入得:
∑ j = 1 n i d × j d × gcd ⁡ ( i , j ) c − d × x j ≡ b i \sum_{j=1}^n i^d \times j^d \times \gcd(i,j)^{c-d} \times x_j \equiv b_i j=1nid×jd×gcd(i,j)cd×xjbi

神仙告诉我们,其实长这样的都可以做:
∑ j = 1 n g ( i ) × h ( j ) × f ( gcd ⁡ ( i , j ) ) × x j ≡ b i \sum_{j=1}^n g(i) \times h(j) \times f(\gcd(i,j)) \times x_j \equiv b_i j=1ng(i)×h(j)×f(gcd(i,j))×xjbi

f ( n ) = ∑ k ∣ n f ′ ( k ) f(n)=\sum_{k|n}f'(k) f(n)=knf(k),由于我们知道 f f f,所以可以大力反演一波得到 f ′ f' f,代入得:
∑ j = 1 n ∑ k ∣ gcd ⁡ ( i , j ) g ( i ) × h ( j ) × f ′ ( k ) × x j ≡ b i ∑ k ∣ i f ′ ( k ) ∑ k ∣ j h ( j ) × x j ≡ b i g ( i ) \begin{aligned} \sum_{j=1}^n \sum_{k|\gcd(i,j)} g(i) \times h(j) \times f'(k) \times x_j &\equiv b_i\\ \sum_{k|i} f'(k) \sum_{k|j} h(j) \times x_j &\equiv \frac {b_i} {g(i)}\\ \end{aligned} j=1nkgcd(i,j)g(i)×h(j)×f(k)×xjkif(k)kjh(j)×xjbig(i)bi

t ( n ) = ∑ n ∣ i h ( i ) × x i t(n)=\sum_{n|i} h(i)\times x_i t(n)=nih(i)×xi,代入有:
∑ k ∣ i f ′ ( k ) t ( k ) ≡ b i g ( i ) \sum_{k|i} f'(k) t(k) \equiv \frac {b_i} {g(i)} kif(k)t(k)g(i)bi

可能这样更顺眼?
b i g ( i ) ≡ ∑ k ∣ i f ′ ( k ) t ( k ) \frac {b_i} {g(i)} \equiv \sum_{k|i} f'(k) t(k) g(i)bikif(k)t(k)

由于我们知道 b i g ( i ) \frac {b_i} {g(i)} g(i)bi,所以再次反演可以求出 f ′ ( k ) t ( k ) f'(k)t(k) f(k)t(k),除掉 f ′ ( k ) f'(k) f(k) 就得到了 t ( k ) t(k) t(k)

然而 t ( k ) t(k) t(k) 也是个莫反的形式,所以最后莫反一波求出 h ( i ) × x i h(i)\times x_i h(i)×xi,然后除掉 h ( i ) h(i) h(i) 就求出 x i x_i xi 了。

由于在这题中 g ( i ) = h ( i ) g(i)=h(i) g(i)=h(i),所以代码里面统一用 g ( i ) g(i) g(i) 了,以及代码里面的 f f f 其实就是柿子里的 f ′ f' f,柿子里的 f f f 其实代码里不需要存下来。
h ( i ) × x i ≡ ∑ i ∣ n μ ( n i ) t ( n ) h(i)\times x_i \equiv \sum_{i|n} \mu(\frac n i)t(n) h(i)×xiinμ(in)t(n)

代码如下:

#include <cstdio>
#define mod 998244353
#define ll long long
#define maxn 300010

int n,c,d,q;
int mu[maxn],prime[maxn],tot=0;
bool v[maxn];
ll ksm(ll x,int y)
{
	ll re=1;
	y=(y%(mod-1)+mod-1)%(mod-1);//如果是负数那么就加上mod-1
	while(y)
	{
		if(y&1)re=re*x%mod;
		x=x*x%mod;y>>=1;
	}
	return re;
}
#define inv(x) ksm(x,-1)
ll b[maxn],g[maxn],f[maxn],t[maxn];
void work()
{
	mu[1]=1;
	for(int i=2;i<=n;i++)//这里真的难受……我以为是莫反打错了调了一个小时,结果是线性筛错了QAQ
	{
		if(!v[i])prime[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&i*prime[j]<=maxn-10;j++)
		{
			v[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++)g[i]=ksm(i,d);//求g 
	for(int i=1;i<=n;i++)if(mu[i]!=0)//求f' 
	for(int j=1;i*j<=n;j++)
	f[i*j]=(f[i*j]+(mod+mu[i]*ksm(j,c-d))%mod)%mod;
}

int main()
{
	scanf("%d %d %d %d",&n,&c,&d,&q);
	work();
	while(q--)
	{
		for(int i=1;i<=n;i++)scanf("%lld",&b[i]),b[i]=b[i]*inv(g[i])%mod;//求出b[i]/g[i] 
		
		for(int i=1;i<=n;i++)t[i]=0;
		for(int i=1;i<=n;i++)if(mu[i]!=0)//求出t[i] 
		for(int j=1;i*j<=n;j++)
		t[i*j]=(t[i*j]+(mod+mu[i]*b[j])%mod)%mod;
		bool v=false;
		for(int i=1;i<=n;i++)
		if(!f[i]&&t[i]){v=true;break;}
		else t[i]=t[i]*inv(f[i])%mod;
		if(v){printf("-1\n");continue;}
		
		for(int i=1;i<=n;i++)b[i]=0;//求出答案,由于此时b数组没用了,我们理所当然地把他拿过来打打工 
		for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j+=i)
		b[i]=(b[i]+(mod+mu[j/i]*t[j])%mod)%mod;
		for(int i=1;i<=n;i++)
		printf("%lld ",b[i]*inv(g[i])%mod);
		printf("\n");
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值