1. 程式人生 > 其它 >題解 noip2018模擬測試賽(三十二)

題解 noip2018模擬測試賽(三十二)

傳送門

Tournament

題目思路

簡單的貪心
顯然一個選手在擊敗 所有由他擊敗的選手 之前不能被其他選手擊敗
那麼我們為這次錦標賽設定一個時間,顯然同一時間內一個選手只能擊敗一人(只能打一次比賽)
\(t_i\) 為一個選手最早能被擊敗的時間(即此時它被擊敗不會與條件衝突)
由於一個選手在同一時間內只能擊敗一人,我們直接貪心即可得到 \(t_i\) ,最後的答案就是 \(t_1\)

程式碼

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n,d[100005];
vector<int>g[100005];
bool cmp(int g1,int g2){
	return d[g1]<d[g2];
}
void dfs(int u){
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		dfs(v);
	}
	sort(g[u].begin(),g[u].end(),cmp);
	int last=-1;
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(d[v]<=last)last++;
		else last=d[v];
	}
	d[u]=last+1;
}
int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x;
		scanf("%d",&x);
		g[x].push_back(i);
	}
	dfs(1);
	printf("%d",d[1]);
	return 0;
}

Building Cubes with AtCoDeer

題目思路

玄學計數題(列舉)
一開始想著類似折半搜尋一樣的思路,每次列舉立方體的三個面,然後狀壓起來。但這樣無法處理兩次列舉到相同瓷磚的情況,放棄
考慮要列舉幾個瓷磚,三個顯然不行,四個時間爆炸,發現當我們列舉到上下兩個面的瓷磚時已經可以確定立方體四個角的顏色了
因此我們列舉立方體上下兩個面的瓷磚,分別確定另外四個面四個角的顏色,提前將他們狀壓起來,直接查詢即可
實現判重時有些細節,注意程式碼;另外注意瓷磚旋轉的情況,可以用畫圖3D畫一畫

程式碼

#include<iostream>
#include<unordered_map>
#define get(a,b,c,d) (a*1000000000ll+b*1000000ll+c*1000ll+d)
#define nxt(x) (x%1000ll*1000000000ll+x/1000ll)
using namespace std;
int n,c[405][4];
long long a[405];
unordered_map<long long,int>m;
void calc(long long x,int d) {
	for(int i=0; i<4; i++) {
		m[x=nxt(x)]+=d;
	}
}
int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		for(int d=0; d<4; d++)scanf("%d",&c[i][d]);
		a[i]=get(c[i][0],c[i][1],c[i][2],c[i][3]);
		calc(a[i],1);
	}
	long long ans=0;
	for(int i=1; i<=n; i++) {
		calc(a[i],-1);
		for(int j=i+1; j<=n; j++) {
			calc(a[j],-1);
			for(int d=0; d<4; d++) {
				long long x0=get(c[j][1],c[j][0],c[i][(d+1)%4],c[i][d]);
				long long x1=get(c[j][2],c[j][1],c[i][d],c[i][(d+3)%4]);
				long long x2=get(c[j][3],c[j][2],c[i][(d+3)%4],c[i][(d+2)%4]);
				long long x3=get(c[j][0],c[j][3],c[i][(d+2)%4],c[i][(d+1)%4]);
				if(m[x0]==0||m[x1]==0||m[x2]==0||m[x3]==0)continue;
				long long mul=1;
				mul*=m[x0],calc(x0,-1);
				mul*=m[x1],calc(x1,-1);
				mul*=m[x2],calc(x2,-1);
				mul*=m[x3],calc(x3,-1);
				calc(x0,1);
				calc(x1,1);
				calc(x2,1);
				calc(x3,1);
				ans+=mul;
			}
			calc(a[j],1);
		}
		calc(a[i],1);
	}
	printf("%lld",ans/3);
	return 0;
}

[HNOI2015]落憶楓音

第一眼想到矩陣樹定理,然而資料範圍過大,不能直接求解
我們考慮這樣一件事情,對於未加邊的有向無環圖,我們按照dfs序給他們重新編號,這樣求出來的矩陣一定是一個上三角矩陣
也就是說對於有向無環圖來說,矩陣的行列式可以 \(O(n)\) 求解,這個值實際就是每個節點入度之積(對於一號節點,我們再建一個零號節點連向他)
也可以這麼理解,即為每個節點選一個父親
然而現在還多加了一條邊,有可能構成環,怎麼辦呢?
我們發現,假如我們仍用上述方法求解,最後得出的生成樹有一部分會帶有環,設環上節點為 \(a_1,a_2\dots a_k\),這部份帶有環的'生成樹'的數量為 \(\frac{\prod\limits_{i=1}^nd_i}{\prod\limits_{i=1}^kd_{a_i}}\)

(因為環上所有節點父親已經確定)
因此我們考慮容斥,設 \(d_i\) 為節點 \(i\) 入度,答案即為 \(\prod\limits_{i=1}^nd_i-\frac{\prod\limits_{i=1}^nd_i}{\prod\limits_{i=1}^kd_{a_i}}\)
考慮dp求解,若連邊為 \(x\rightarrow y\),則設 \(f_u\) 為若連邊為 \(x\rightarrow u\)\(num\) 的值
那麼 \(f_u=\frac{1}{d[u]}\sum f_v\)
直接求解即可

程式碼

#include<iostream>
#include<vector>
using namespace std;
const int mod=1e9+7;
int n,m,x,y,d[100005];
vector<int>g[100005];
long long fpow(long long x,int y) {
	long long res=1;
	while(y) {
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
long long sum=1,f[100005];
bool vis[100005];
void dfs(int u) {
	vis[u]=true;
	if(u==x){
		f[u]=sum*fpow(d[u],mod-2);
		return;
	}
	for(int i=0; i<g[u].size(); i++) {
		int v=g[u][i];
		if(!vis[v])dfs(v);
		f[u]=(f[u]+f[v])%mod;
	}
	f[u]=f[u]*fpow(d[u],mod-2)%mod;
}
int main() {
	scanf("%d%d%d%d",&n,&m,&x,&y);
	d[y]++;
	for(int i=1; i<=m; i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		d[v]++;
	}
	d[1]++;
	for(int i=1; i<=n; i++) {
		sum=sum*d[i]%mod;
	}
	dfs(y);
	printf("%lld",(sum-f[y]+mod)%mod);
	return 0;
}