1. 程式人生 > >dfs+基環樹上dp--HDU 6403 Card Game

dfs+基環樹上dp--HDU 6403 Card Game

闆闆講的題!

把每張牌看成一條邊,正面向反面連權值為1的邊,反之為0

每個點只能有<=1個入度

可以看出只有樹或者基環樹才是合法的

所以就只要在樹或者基環樹上dp就好了

這個dp可以先定一個根,因為一定有一個點是入度為0的,先求出這個值,然後可以dp求出以其他點為根的值

最後答案取最小的,方案數也可以相應算出

程式碼如下:(沒有壓行的106quq)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define maxn 200005
#define LL long long
#define inf 0x3f3f3f3f
using namespace std;
int T,n,cnt,head[maxn],a[maxn],sz,ed,s,t,pos;
LL f[maxn],g[maxn],ans,ans2,num;
vector<int> vec;
const int mod=998244353;
bool vis[maxn],flg,cir[maxn];

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

struct EDGE{
	int to,nxt,w;
}edge[maxn<<1];

inline void add(int x,int y,int z){
	edge[++cnt].to=y; edge[cnt].nxt=head[x]; edge[cnt].w=z; head[x]=cnt;
}

inline void init(){
	cnt=1,ans=0,ans2=1; memset(head,0,sizeof head); memset(vis,0,sizeof vis);
}

inline void calc(int u){
	vis[u]=1; sz++;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to; ed++;
		if(!vis[v]) calc(v);
	}
	return;
}

inline void dfs(int u,int fa){
	vis[u]=1; f[u]=0;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to; if(v==fa) continue;
		if(!vis[v]) {
			dfs(v,u);
			f[u]+=f[v]+edge[i].w;
		}
		else s=u,t=v,pos=i;
	}
}

inline void DP(int u,int fa){
	vec.push_back(g[u]);
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to; if(v==fa)continue;
		if(i==pos || i==(pos^1)) continue;
		g[v]=g[u]+(edge[i].w?-1:1);//以v為根的話原來是1的邊要-1,是0要+1 
		DP(v,u);
	}
}

int main(){
	T=rd();
	while(T--){
		n=rd(); init(); flg=true;
		for(int i=1;i<=n;i++){
			int x=rd(),y=rd();
			add(x,y,1); add(y,x,0);
		}
		for(int i=1;i<=2*n;i++)
			if(!vis[i]){
				sz=ed=0; calc(i);//判斷每個聯通塊是不是樹or基環樹 
				if(ed/2>sz) {flg=false;break;}
			}
		if(!flg) {puts("-1 -1");continue;}
		memset(vis,0,sizeof vis);
		for(int i=1;i<=2*n;i++)
			if(!vis[i]){
				s=t=pos=-1; num=0; vec.clear();
				dfs(i,0); g[i]=f[i];//dfs找到環,首先計算以i為根的值 
				DP(i,0);//再dp以其他點為根的值 
				if(s==-1){//無環 
					sort(vec.begin(),vec.end());
					for(int j=0;j<vec.size();j++){
						if(vec[j]!=vec[0]) break;
						num++;
					}
					ans+=vec[0];
				}
				else{
					pos%=2;//看環上哪個方向值更小 
					if(g[s]+pos==g[t]+(pos^1)) num=2;
					else num=1;
					ans+=min(g[s]+pos,g[t]+(pos^1));
				}
				ans2=ans2*num%mod;//方案數 
			}
		printf("%lld %lld\n",ans,ans2);
	}
	return 0;
}