动态规划专项训练记录 2024.3

Paths on the Tree

若使分数最大,则尽量每条路径都到叶子,看到题目说绝对值差不超过1,可以发现是要尽量平均分配,设余r条路径

既然要最大化贡献且剩下的路径要不重复的分配,那就选取前r条从该节点到叶子节点权值和最大的链,递归求取

但有一种情况,若在点u选了路径t,在fa再次选择,就会不满足,所以向上返回时应返回第r+1大的路径

代码:

#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;
int n,T,k,head[200005],edgenum;
ll a[200005];
struct edge{
	int to,nxt;
}e[400005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
ll ans;
void init()
{
	edgenum=ans=0;
	memset(head,0,sizeof(head));
}
int son[200005];
void dfs(int u,int fa)
{
	son[u]=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		son[u]++;
	}
}
ll dfs2(int u,int k,int fa)
{
	ans+=1ll*k*a[u];
	if(!son[u]) return a[u];
	int t=k/son[u],r=k%son[u];
	priority_queue<ll>q;
	while(!q.empty()) q.pop();
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		q.push(dfs2(v,t,u));
	}
	while(r--)
	{
		ans+=q.top();
		q.pop();
	}
	return q.top()+a[u];
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		init();
		for(int i=2;i<=n;i++)
		{
			int fa;
			scanf("%d",&fa);
			add_edge(i,fa);
			add_edge(fa,i);
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
		}
		dfs(1,0);
		//printf("1");
		int tmp=dfs2(1,k,0);
		printf("%lld\n",ans);
	}
	return 0;
}

[abc259_f]Select Edges

dpi,1/0表示第i个点向/不向父亲连边的最大值

考虑转移,先默认都不连,接着考虑连哪些,很明显,差越大,连上的收益就越大

所以考虑优先队列(大根堆),储存dp[v][1]+e[i].val-dp[v][0]中的正值,dp[u][1]取前d[u]-1项,dp[u][0]取前d[u]项

代码:

#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
int n,d[300005],head[300005],edgenum;
struct edge{
	int to,nxt,val;
}e[600005];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
ll dp[300005][2];
void dfs(int u,int fa)
{
	priority_queue<ll>q;
	dp[u][0]=dp[u][1]=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		q.push(dp[v][1]+1ll*e[i].val-dp[v][0]);
		dp[u][0]+=dp[v][0];
	}
	if(!d[u])
	{
		dp[u][1]=-1e18;
		return;
	}
	dp[u][1]=dp[u][0];
	int r=0;
	while(!q.empty()&&r+1<d[u])
	{
		if(q.top()>=0)
		{
			dp[u][1]+=q.top();
		}
		else break;
		q.pop();
		r++;
	}
	dp[u][0]=dp[u][1];
	if(d[u]&&!q.empty()&&q.top()>=0) dp[u][0]+=q.top();
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&d[i]);
	}
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	dfs(1,0);
	printf("%lld",dp[1][0]);
	return 0;
}

Minimal Coverage

可以发现,若总长为l可以,则总长大于l一定可以,所以可以二分

接着考虑check函数,可以记录可能的终点,设dpi,j表示目前是第i个线段,末尾为j的状态,0表示不能,1表示可以,有:

dpi,j=dpi1,jai|dpi1,j+ai

代码:

#include<cstdio>
using namespace std;
int n,T,a[10004];
bool dp[10005][2005];
bool check(int x)
{
	for(int i=0;i<=x;i++)
	{
		dp[0][i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=x;j++)
		{
			dp[i][j]=0;
			if(j>=a[i]) dp[i][j]|=dp[i-1][j-a[i]];
			if(j+a[i]<=x) dp[i][j]|=dp[i-1][j+a[i]];
		}
	}
	for(int i=0;i<=x;i++)
	{
		if(dp[n][i]) return 1;
	}
	return 0;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]); 
		}
		int l=1,r=2000;
		while(l<r)
		{
			int mid=(l+r)>>1;
			if(check(mid))
			{
				r=mid;
			}
			else
			{
				l=mid+1;
			}
		}
		printf("%d\n",l);
	}
	return 0;
}

FTL

gi表示总伤害为i时的所用最少时间,设fi为通过若干单发 + 最后恰好一次双炮连发造成i的伤害的最小时间

很明显:gi=minj=0i1gj+fij

对于fi,考虑枚举第一个的发射次数j,则第二个发射次数最小为ij(p1s)(p1+p2s)p2s,再加上一起发射的时间,取max即可

最终答案是mini=hgi

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll p1,t1,p2,t2,h,s;
ll f[10003],g[10003],ans;
int main()
{
	scanf("%lld%lld",&p1,&t1);
	scanf("%lld%lld",&p2,&t2);
	scanf("%lld%lld",&h,&s);
	ans=min(t1*((h-1)/(p1-s)+1),t2*((h-1)/(p2-s)+1));
	for(int i=1;i<=h+max(p1,p2);i++)
	{
		f[i]=g[i]=1e18;
	}
	for(int i=1;i<=h+max(p1,p2);i++)
	{
		for(int j=0;j*(p1-s)+(p1+p2-s)<=i;j++)
		{
			int k;
			if(i-j*(p1-s)-(p1+p2-s)!=0)
			k=(i-j*(p1-s)-(p1+p2-s)-1)/(p2-s)+1;
			else k=0;
			f[i]=min(f[i],max(t1*(j+1),t2*(k+1)));
		}
		for(int j=0;j<i;j++)
		{
			g[i]=min(g[i],f[i-j]+g[j]);
		}
	//	printf("%d %lld %lld\n",i,f[i],g[i]);
		if(i>=h) ans=min(ans,g[i]);
	}
	printf("%lld",ans);
	return 0;
}

Maximum Weight Subset

dpi,j表示在i的子树中,选的点与i的距离不小于j的最大权值

1.选根节点: dpi,0=json(i)dpj,k

2.不选根节点: dpi,d=maxvson(i)(dpv,d1+toson(i)dpto,max(kd,d1))

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,a[205],edgenum,head[205];
struct edge{
	int to,nxt;
}e[405];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int f[205][205];
void dfs(int u,int fa)
{
	f[u][0]=a[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		f[u][0]+=f[v][k];
		for(int d=1;d<=k;d++)
		{
			int dis=f[v][d-1];
			for(int j=head[u];j;j=e[j].nxt)
			{
				int w=e[j].to;
				if(v==w||w==fa) continue;
				dis+=f[w][max(d-1,k-d)];
			}
			f[u][d]=max(f[u][d],dis);
		}
	}
	for(int i=k;i>0;i--)
	{
		f[u][i-1]=max(f[u][i-1],f[u][i]);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	printf("%d",f[1][0]);
	return 0;
}

Sonya and Informatics

设0的个数为m,fi,j表示在i次交换后,前m位有j个0的方案数

分三种情况

  1. 0变少了,则前m个位置的0与后(n-m)个位置的1调换,方案数:j(n2m+j)
  2. 0变多了,则前m个位置的1与后(n-m)个位置的0调换,方案数:(mj)2
  3. 其余情况,即0的个数不变,方案数fracn(n1)2j(n2m+j)(mj)2

考虑优化,设Ai=(mi)2,Bi=i(n2m+i),Ci=fracn(n1)2j(n2m+j)(mj)2,因为转移只与fi1有关,所以考虑矩阵乘法

[fk,0fk,1fk,m]=[C0A0000B1C1A100000BmCm]k×[f0,0f0,1f0,m]

代码:

#include<cstdio>//
#include<cstring>//
#define ll long long
using namespace std;
const int p=1e9+7;
int n,m,cnt,sum,t[105];
struct mat{
	ll a[105][105];
	void clear()
	{
		memset(a,0,sizeof(a));
	}
	mat operator * (const mat x)const{
		mat ans;
		ans.clear();
		for(int i=0;i<=sum;i++)
		{
			for(int j=0;j<=sum;j++)
			{
				for(int k=0;k<=sum;k++)
				{
					ans.a[i][j]+=a[i][k]*x.a[k][j]%p;
					ans.a[i][j]%=p;
				}
			}
		}
		return ans;
	} 
}Mat;
mat qpowMat(mat x,int y)
{
	mat res;
	res.clear();
	for(int i=0;i<=sum;i++)
	{
		res.a[i][i]=1;
	}
	while(y)
	{
		if(y&1) res=res*x;
		x=x*x;
		y>>=1;
	}
	return res;
}
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t[i]);
		if(t[i]==0) sum++;
	}
	for(int i=1;i<=sum;i++)
	{
		if(t[i]==0) cnt++;
	}
	for(int i=0;i<=sum;i++)
	{
		int A=1ll*(sum-i)*(sum-i)%p;
		int B=1ll*i*(n-2*sum+i)%p;
		int C=(1ll*n*(n-1)/2-A-B)%p;
		if(i!=sum) Mat.a[i][i+1]=A;
		if(i!=0) Mat.a[i][i-1]=B;
		Mat.a[i][i]=C; 
	}
	Mat=qpowMat(Mat,m);
	printf("%lld",qpow(qpow(n*(n-1)/2,m),p-2)*Mat.a[cnt][sum]%p);
	return 0;
}

Square Subsets

因为完全平方数的因子均有偶数个,而70以内只有19个质数,考虑状压,设目前是第i个数,每个质因子的奇偶状态为j,方案数直接由dp[i-1][j^state[i]]和dp[i-1][j]即可

但n很大,ai很小,所以记值为i的数的个数为ti,所以,当ai取奇数个时才影响结果

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int pr[25]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79};
int n,a[100005],t[75],state[75];
int dp[72][524290],fac[100005],inv[100005];
int qpow(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1) res=1ll*res*x%p;
		x=1ll*x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int m,int n)
{
	if(m>n) return 0;
	return 1ll*fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d",&n);
	fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=1ll*fac[i-1]*i%p;
	}
	inv[n]=qpow(fac[n],p-2);
	for(int i=n-1;i>=0;i--)
	{
		inv[i]=1ll*inv[i+1]*(i+1)%p;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		t[a[i]]++;
		if(state[a[i]]) continue;
 		for(int j=0;j<19;j++)
		{
			int cnt=0;
			int v=a[i];
			while(v%pr[j]==0)
			{
				v/=pr[j];
				cnt++;
			}
			state[a[i]]|=((cnt%2)<<j);
		}
	}
	dp[0][0]=1;
	for(int i=0;i<70;i++)
	{
		ll a=0,b=0;
		for(int j=0;j<=t[i+1];j++)
		{
			if(j%2) b=(b+C(j,t[i+1]))%p;
			else a=(a+C(j,t[i+1]))%p;
		}
		for(int j=0;j<(1<<19);j++)
		{
			dp[i+1][j]=(1ll*dp[i+1][j]+1ll*dp[i][j]*a%p)%p;
			dp[i+1][j^state[i+1]]=(1ll*dp[i+1][j^state[i+1]]+1ll*dp[i][j]*b%p)%p;
		}
	}
	printf("%d",(dp[70][0]-1+p)%p);
	return 0;
}

Pillars

dpi表示跳到第i个柱子的最多经过数,则

dpi=maxj=1i1dpj×[|aiaj|>=d]+1

用值域线段树优化即可

代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
int n,rt,ans,ed;
ll a[100005],d,p=1e15;
int dp[100005];
int ls[6400005],rs[6400005],sum[6400005],cnt;
int update(int id,ll l,ll r,ll x,int c)
{
	if(l>x||x>r) return id;
	if(!id) id=++cnt; 
	if(l==r)
	{
		sum[id]=max(sum[id],c);
		return id;
	}
	ll mid=(l+r)>>1;
	ls[id]=update(ls[id],l,mid,x,c);
	rs[id]=update(rs[id],mid+1,r,x,c);
	sum[id]=max(sum[ls[id]],sum[rs[id]]);
	return id;
}
int query(int id,ll l,ll r,ll x,ll y)
{
	if(l>y||x>r||x>y) return 0;
	if(!id) return 0;
	if(x<=l&&y>=r)
	{
		return sum[id];
	}
	ll mid=(l+r)>>1;
	return max(query(ls[id],l,mid,x,y),query(rs[id],mid+1,r,x,y));
}
int ans1[100005];
int main()
{
	scanf("%d%lld",&n,&d);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		int x=query(rt,1,p,1,a[i]-d);
		int y=query(rt,1,p,a[i]+d,p);
		dp[i]=max(x,y)+1;
		//printf("%d %d %d\n",x,y,dp[i]);
		if(dp[i]>ans)
		{
			ans=dp[i];
			ed=i;
		}
		rt=update(rt,1,p,a[i],dp[i]);
	}
	printf("%d\n",ans);
	int now=ed,x=1;
	ans1[1]=ed;
	for(int i=ed;i>0;i--)
	{
		if(abs(a[now]-a[i])>=d&&dp[i]+1==dp[now])
		{
			now=i;
			ans1[++x]=i;
		}
	}
	for(int i=x;i>0;i--)
	{
		printf("%d ",ans1[i]);
	}
	return 0;
}

Magic Gems

题意是求一个01串s,0的个数记为p,1的个数记为q,使p+mq=n,求s的个数

fi表示p+mq=i时的方案数,因为可以在已有串的基础上加一个0或一个1,所以方案数为:

fi=fi1+fim

时间复杂度O(n),考虑矩阵快速幂优化

[fifi+1fi+m1]=[110001000000010]n×[fimfim+1fi1]

代码:

#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int p=1e9+7;
ll n;
int m;
struct mat{
	ll a[105][105];
	void clear()
	{
		memset(a,0,sizeof(a));
	}
	mat operator * (const mat &x)const{
		mat ans;
		ans.clear();
		for(int i=1;i<=m;i++)
		{
			for(int j=1;j<=m;j++)
			{
				for(int k=1;k<=m;k++)
				{
					ans.a[i][j]+=a[i][k]*x.a[k][j];
					ans.a[i][j]%=p;
				}
			}
		}
		return ans;
	}
}t;
mat qpow(mat x,ll y)
{
	mat res;
	res.clear();
	for(int i=1;i<=m;i++) res.a[i][i]=1;
	while(y)
	{
		if(y&1) res=res*x;
		x=x*x;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%lld%d",&n,&m);
	t.a[1][1]=t.a[1][m]=1;
	for(int i=2;i<=m;i++)
	{
		t.a[i][i-1]=1;
	}
	mat ans=qpow(t,n);
	printf("%d",ans.a[1][1]);
	return 0;
}

Steps to One

不会莫比乌斯反演,写个分解质因数的解吧

fi表示最大公约数为i时的数组期望长度,则:

fi=1+j=1mfgcd(i,j)m

gcd(i,j)=i,则j为i的倍数,这样的j有mi个,所以式子为:

fi=mmmi+j=1mfgcd(i,j)mmi×[jmod i0]

考虑优化,可以用容斥原理记录与i的最大公约数为j的数的个数,直接求解即可

代码:

#include<cstdio>
#include<vector>
#define ll long long
using namespace std;
const int p=1e9+7;
int m;
ll f[100005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
vector<int>v[100005],dp[100005];
int main()
{
	scanf("%d",&m);
	f[1]=0;
	for(int i=m;i>0;i--)
	{
		for(int j=i;j<=m;j+=i)
		{
			v[j].push_back(i);
			dp[j].push_back(0);
		}
	}
	f[1]=1;
	for(int i=2;i<=m;i++)
	{
		for(int j=0;j<v[i].size();j++)
		{
			dp[i][j]+=m/v[i][j];
			for(int k=j+1;k<v[i].size();k++)
			{
				if(v[i][j]%v[i][k]==0) dp[i][k]-=dp[i][j];
			}
		}
		f[i]=qpow(m-dp[i][0],p-2)*m%p;
		for(int j=1;j<v[i].size();j++)
		{
			f[i]=(f[i]+f[v[i][j]]*dp[i][j]%p*qpow(m-dp[i][0],p-2)%p)%p;
		}
	}
	ll ans=0;
	for(int i=1;i<=m;i++) ans=(ans+f[i])%p;
	printf("%lld",ans*qpow(m,p-2)%p);
	return 0;
}

Jellyfish and EVA

pu表示u点走到n的概率,设fu为从fa走到u的概率,将所有v按pv降序排列,则:

pu=vson(u)pv×fv

考虑如何求fu

gi表示在有i个点的情况下的最优f序列,那么有 g1,1=1,g2,1=0.5,g2,2=0

显然,gi,1=1i

考虑转移,若转移到的点不是v1,则前面必然转移失败且j未被删除,设另一个被删除的点为vk

  • 若j<k,那么j从当前的第j优节点转化为第j-1优节点,有gi,j=gi2,j1
  • 若j>k,那么j从当前的第j优节点转化为第j-2优节点,有gi,j=gi2,j2

所以方程为:

gi,j=gi2,j1×j2i+gi2,j2×iji

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5000;
int n,m,d[5005],T,head[5005],edgenum;
double g[5005][5005],p[5005],b[5005];
bool cmp(double a,double b)
{
	return a>b;
}
struct edge{
	int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
bool vis[5005];
void init()
{
	memset(head,0,sizeof(head));
	memset(d,0,sizeof(d));
	memset(vis,0,sizeof(vis));
	edgenum=0;
}
void dfs(int u)
{
	if(vis[u]) return;
	double a[5005];
	int id=0;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		dfs(v);
		a[++id]=p[v];
	}
	sort(a+1,a+id+1);
	p[u]=0.0;
	for(int i=1;i<=id;i++)
	{
		p[u]+=g[id][i]*a[i];
	}
	if(u==n) p[u]=1.0;
}
int main()
{
	g[1][1]=1,g[2][2]=0.5;
	for(int i=3;i<=N;i++)
	{
		b[1]=g[i][1]=1.0/(i*1.0);
		for(int j=2;j<=i;j++)
		{
			g[i][j]=g[i-2][j-2]*(j-2)/(i*1.0)+g[i-2][j-1]*(i-j)/(i*1.0);
			b[j]=g[i][j];
		}
		sort(b+1,b+i+1);
		for(int j=1;j<=i;j++) g[i][j]=b[j];
	}
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add_edge(u,v);
		}
		dfs(1);
		printf("%.12lf\n",p[1]);
	}
	return 0;
}

Transitive Graph

可以发现,因为会重复连边的过程,若原图a可以到达b,连边后a,b可直达,所以在一个强连通分量中,连边后是完全图

所以,在强连通分量中,可以从任意点进,任意点出,且不重不漏走完所有点

所以可以先缩点,再在DAG上跑DP

代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,T,a[200005],head[200005],edgenum,x[200005],y[200005];
struct edge{
	int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int dfn[200005],low[200005],idx,belong[200005];
int scc,top,s[200005];
bool ins[200005];
void tarjan(int i)
{
	dfn[i]=low[i]=++idx;
	s[++top]=i;
	ins[i]=1;
	for(int u=head[i];u;u=e[u].nxt)
	{
		int j=e[u].to;
		if(!dfn[j])
		{
			tarjan(j);
			low[i]=min(low[i],low[j]);
		}
		else if(ins[j])
		{
			low[i]=min(low[i],dfn[j]);
		}
	}
	int v=0;
	if(low[i]==dfn[i])
	{
		scc++;
		while(v!=i)
		{
			v=s[top];
			top--;
			belong[v]=scc;
			ins[v]=0;
		}
	}
}
int d[200005],dis[200005],size[200005];
ll val[200005],sccval[200005],ans_dis,ans_val;
queue<int>q;
void init(int x)
{
	for(int i=1;i<=x;i++)
	{
		dis[i]=size[i]=val[i]=sccval[i]=0;
		d[i]=head[i]=0;
		belong[i]=dfn[i]=low[i]=0;
	}
	scc=top=edgenum=idx=ans_dis=ans_val=0;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		init(n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&x[i],&y[i]);
			if(x[i]!=y[i]) add_edge(x[i],y[i]);
		}
		for(int i=1;i<=n;i++)
		{
			if(!dfn[i]) tarjan(i);
			size[belong[i]]++;
			sccval[belong[i]]=sccval[belong[i]]+a[i];
		}
		for(int i=1;i<=n;i++) head[i]=0;
		edgenum=0;
		for(int i=1;i<=m;i++)
		{
			int u=belong[x[i]],v=belong[y[i]];
			if(u!=v)
			{
				add_edge(u,v);
				d[v]++;
			}
		}
		while(!q.empty()) q.pop();
		for(int i=1;i<=scc;i++)
		{
			if(d[i]==0) q.push(i);
			dis[i]=size[i];
			val[i]=sccval[i];
		}
		while(!q.empty())
		{
			int u=q.front();
			q.pop();
			for(int i=head[u];i;i=e[i].nxt)
			{
				int v=e[i].to;
				if(dis[v]==dis[u]+size[v])
				{
					val[v]=min(val[v],sccval[v]+val[u]);
				}
				if(dis[v]<dis[u]+size[v])
				{
					dis[v]=dis[u]+size[v];
					val[v]=sccval[v]+val[u];
				}
				d[v]--;
				if(d[v]==0) q.push(v);
			}
		}
		for(int i=1;i<=scc;i++)
		{
			if(dis[i]==ans_dis)
			{
				ans_val=min(val[i],ans_val);
			}
			if(dis[i]>ans_dis)
			{
				ans_dis=dis[i];
				ans_val=val[i];
			}
		}
		printf("%lld %lld\n",ans_dis,ans_val);
	}
	return 0;
}

Playoff Fixing

题目不算难懂,它是类似于线段树取min值的过程

假设当前有2n支队伍,则在相邻的两支队伍中必然有一支编号大于n,另一支小于等于n

考虑依次安排淘汰的队伍,设有x个组只确定了1个,k个组确定了0个,则方案数为:x!×2k

由乘法原理,因为已经被淘汰,所以互干涉,直接相乘即可

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=998244353;
int m,a[1000005],vis[1000005],vis2[1000005];
ll fac[1000005],ans=1;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&m);
	int n=1<<m;
	for(int i=1;i<=n;i++) a[i]=-1;
	for(int i=1;i<=n;i++)
	{
		int tmp;
		scanf("%d",&tmp);
		if(tmp!=-1) a[tmp]=i;
	}
	fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	for(int i=1;i<=m;i++)
	{
		int cnt=0,tot=0;
		for(int j=(1<<(i-1))+1;j<=(1<<i);j++)
		{
			int tmp=(a[j]-1)>>(m-i+1);
			if(a[j]==-1) continue;
			if(vis[tmp]==i)
			{
				printf("0");
				return 0;
			}
			vis[tmp]=i;
			cnt++;
		}
		for(int j=1;j<=(1<<(i-1));j++)
		{
			int tmp=(a[j]-1)>>(m-i+1);
			if(a[j]==-1) continue;
			if(vis2[tmp]==i)
			{
				printf("0");
				return 0;
			}
			vis2[tmp]=vis[tmp]=i;
		}
		for(int j=0;j<(1<<(i-1));j++)
		{
			if(vis[j]==i)  tot++; 
		}
		ans=ans*fac[(1<<(i-1))-cnt]%p*qpow(2,(1<<(i-1))-tot)%p;
	}
	printf("%lld",ans);
	return 0;
}

posted @   wangsiqi2010916  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示