山西胡策 #7

A.html

題意:給一個有向圖無環連通圖,求添加一條邊X->Y後有向生成樹的方案數。(n<=100000)c++

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005, mo=1000000007;
int ihead[N], cnt, n, m, X, Y;
struct E { int next, to; }e[N<<1];
void add(int x, int y) { e[++cnt]=(E){ihead[x], y}; ihead[x]=cnt; }
int inv[N], in[N], d[N], q[N], front, tail, f[N];
int main() {
	scanf("%d%d%d%d", &n, &m, &X, &Y);
	inv[1]=1;
	for(int i=2; i<=n; ++i) inv[i]=(-(ll)(mo/i)*inv[mo%i]%mo+mo)%mo;
	for(int i=0; i<m; ++i) { int x, y; scanf("%d%d", &x, &y); add(x, y); ++in[y]; }
	memcpy(d, in, sizeof(int)*(n+1));
	++in[Y];
	int ans=1;
	for(int i=2; i<=n; ++i) ans=(ll)ans*in[i]%mo;
	if(Y==1) { printf("%d\n", ans); return 0; }
	for(int i=1; i<=n; ++i) if(!d[i]) q[tail++]=i;
	f[Y]=ans;
	while(front!=tail) {
		int x=q[front++];
		f[x]=(ll)f[x]*inv[in[x]]%mo;
		for(int i=ihead[x]; i; i=e[i].next) { (f[e[i].to]+=f[x])%=mo; if(!--d[e[i].to]) q[tail++]=e[i].to; }
	}
	printf("%d\n", (ans-f[X]+mo)%mo);
	return 0;
}

神題啊= =只會20分暴力有木有啊。。(HNOI就是強。。算法

考慮不加邊,因爲有向無環且連通,那麼考慮除了1之外每一個點都從取一條入度邊,那麼顯然是一種方案。全部的乘積即爲全部方案。所以答案爲$\sum_{i=2}^{n} d(i)$,其中$d(i)$表示$i$的入度。post

考慮加邊,首先考慮上面的作法,那麼答案顯然是多加了的,而多加的部分就是由一個新環以及環外其餘點組成的生成樹的方案。因爲環上必定存在X->Y這條邊,所以咱們只須要計算原圖Y->X的路徑數及其答案便可,即令spa

$$f(i) = \sum_{Y->i的路徑 \\ 且通過的點集爲S} \prod_{j>1且j \notin S} d(j)$$htm

因爲原圖爲拓撲圖,所以按照拓撲序dp便可,即blog

$$f(i) = \sum_{\exists (j, i)} \frac{f(j)}{d(i)}$$排序

最後特判一下Y=1的狀況= =get

B.it

題意:給一棵n個點的樹,給出P條帶權路徑,Q次詢問,每次詢問一條路徑,問這條路徑包含的全部帶權路徑中第k小的權值。(n,Q,P<=40000)

#include <bits/stdc++.h>
using namespace std;
const int N=40005;
int ihead[N], cnt, n, P, Q, FF[N], LL[N], ans[N], tot, now[N], K[N], w[N], c[N];
struct E { int next, to; }e[N<<1];
struct D { int x, y, zf, c, id; }d[N*9];
void add(int x, int y) { e[++cnt]=(E){ihead[x], y}; ihead[x]=cnt; e[++cnt]=(E){ihead[y], x}; ihead[y]=cnt; }
void add1(int x, int y, int zf, int c, int id) { d[++tot]=(D){x, y, zf, c, id}; }
void add2(int x1, int x2, int y1, int y2, int id) {
	add1(x1, y1, 1, 0, id);
	add1(x1, y2+1, -1, 0, id);
	add1(x2+1, y1, -1, 0, id);
	add1(x2+1, y2+1, 1, 0, id);
}
void dfs(int x, int f=-1) {
	static int ID=0;
	FF[x]=++ID;
	for(int i=ihead[x]; i; i=e[i].next) if(e[i].to!=f) dfs(e[i].to, x);
	LL[x]=ID;
}
bool cmp(const D &a, const D &b) { return a.x==b.x?(a.y==b.y?a.c<b.c:a.y<b.y):a.x<b.x; }
void upd(int x, int s) { for(; x<=n; x+=x&-x) c[x]+=s; }
int sum(int x) { int r=0; for(; x; x-=x&-x) r+=c[x]; return r; }
void fz(int l, int r, int L, int R) {
	static D t[N*9];
	if(l==r) { for(int i=L; i<=R; ++i) if(d[i].c) ans[d[i].id]=l; return; }
	sort(d+L, d+R+1, cmp);
	int mid=(l+r)>>1, ct=0, f1=0, f2=0, nl, nr;
	for(int i=L; i<=R; ++i) {
		if(d[i].c) now[d[i].id]=sum(d[i].y), ct+=now[d[i].id]>=K[d[i].id];
		else if(w[d[i].id]<=mid) upd(d[i].y, d[i].zf), ++ct;
	}
	//printf("mid:%d\n", mid);
	//for(int i=L; i<=R; ++i) if(d[i].c) printf("id:%d : %d\n", d[i].id, now[d[i].id]);
	nl=L, nr=L+ct;
	for(int i=L; i<=R; ++i)
		if(!d[i].c) {
			if(w[d[i].id]<=mid) t[nl++]=d[i], upd(d[i].y, -d[i].zf);
			else t[nr++]=d[i];
		}
		else {
			if(now[d[i].id]>=K[d[i].id]) t[nl++]=d[i], f1=1;
			else t[nr++]=d[i], f2=1, K[d[i].id]-=now[d[i].id]; //注意剪掉= =
			now[d[i].id]=0;
		} // printf("%d %d\n", nl, nr); puts("");
	for(int i=L; i<=R; ++i) d[i]=t[i];
	if(f1) fz(l, mid, L, nl-1);
	if(f2) fz(mid+1, r, nl, R);
}
int main() {
	scanf("%d%d%d", &n, &P, &Q);
	for(int i=0; i<n-1; ++i) { int x, y; scanf("%d%d", &x, &y); add(x, y); }
	dfs(1);
	for(int j=1; j<=P+Q; ++j) {
		int x, y, c; scanf("%d%d%d", &x, &y, &c);
		if(FF[x]>FF[y]) swap(x, y);
		if(j>P) add1(FF[x], FF[y], 1, 1, j-P), K[j-P]=c;
		else {
			w[j]=c;
			if(LL[x]<LL[y]) add2(FF[x], LL[x], FF[y], LL[y], j);
			else {
				int z=-1;
				for(int i=ihead[x]; i; i=e[i].next) if(FF[e[i].to]>FF[x] && FF[e[i].to]<=FF[y] && FF[y]<=LL[e[i].to]) z=e[i].to;
				add2(1, FF[z]-1, FF[y], LL[y], j);
				add2(FF[y], LL[y], LL[z]+1, n, j);
			}
		}
	}
	fz(1, 1e9, 1, tot);
	for(int i=1; i<=Q; ++i) printf("%d\n", ans[i]);
	return 0;
}

好神的題= =(HNOI太神啦。。

本題須要想到:

一、用dfs序來表示路徑覆蓋。

二、二分答案

三、總體二分

首先考慮dfs序覆蓋,約定,對於一個點$x$,$FF(x)$爲dfs序,$LL(x)$爲子樹訪問完後此時的dfs序

考慮一條路徑$(x, y)$,$FF(x)<=FF(y)$。

令$f=lca(x, y)$

1. $x!=f$,此時能覆蓋路徑$(x, y)$的路徑必定是從$x$的子樹中取一個點(包括本身)和從$y$的子樹取一個點而後造成的路徑,所以dfs序的表示就是路徑$(a, b)$能覆蓋路徑$(x, y)$的充要條件是$FF(a) \in [FF(x), LL(x)], FF(b) \in [FF(y), LL(y)]$

2. $x==f$,此時能覆蓋路徑$(x, y)$的路徑$(a, b)$必定是$FF(a) \in [1, FF(z)-1], FF(b) \in [FF(y), LL(y)]$或者$FF(a) \in [FF(y), LL(y)], FF(b) \in [LL(z)+1, n]$,$z$表示$x$包含$y$的子樹的根。容易發現前者和後者將全部狀況包含了。

那麼對於一個詢問$(x, y)$,$FF(x)<=FF(y)$(注意保持順序,由於上面的分析都是左小右大的,不然會多算),將他們當作二元座標看待。因而咱們二分權值,每一次都找有多少個權值小於等於當前答案的能覆蓋這個點的矩形便可。這是經典問題= =先按x軸排序後後用bit維護y便可。

但是這裏不少組詢問= =每一次詢問都二分一次太浪費= =因而咱們採用總體二分,就是一次二分統計所有的。(相似cdq分治= =或者說= =cdq分治原本就是總體二分的一種狀況。。)

這個就簡單了,按照如上算法作就好了。。整體複雜度爲$O(nlogn)$($log 10^9$ 這是一個常數【捂臉熊】。這個複雜度彷佛要均攤分析啊= =大概就是最終都會落到$O(Q)$個點)

而後注意一下個人sb錯。。。

一、我總體二分的時候分到右半區間的第K小沒有減去當前的獲得的。。所以後面分治的時候就錯了= =(由於前半區間的插入並無到後半區間來= =)

二、插入矩形的時候X軸和Y軸搞錯= =

 

C.

題意:給出(i, j)之類的約束表示要j必須先i,問1儘可能靠前、2儘可能靠前、3儘可能靠前以此類推的最優方案,或輸出無解。

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int cnt, ihead[N], in[N], ans[N], tot, n, m;
struct E { int next, to; }e[N];
void add(int x, int y) { e[++cnt]=(E){ihead[x], y}; ihead[x]=cnt; }
priority_queue<int> q;

int main() {
	int T; scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &m); tot=0;
		for(int i=0; i<m; ++i) { int x, y; scanf("%d%d", &x, &y); add(y, x); in[x]++; }
		while(q.size()) q.pop();
		for(int i=1; i<=n; ++i) if(in[i]==0) q.push(i);
		while(q.size()) {
			int x=q.top(); q.pop(); ans[++tot]=x;
			for(int i=ihead[x]; i; i=e[i].next) if(--in[e[i].to]==0) q.push(e[i].to);
		}
		if(tot!=n) puts("Impossible!");
		else { for(int i=tot; i>=1; --i) printf("%d ", ans[i]); puts(""); }
		memset(ihead, 0, sizeof(int)*(n+1));
		memset(in, 0, sizeof(int)*(n+1));
		cnt=0;
	}	
	return 0;
}

亂搞才5分啊QAQ我居然忘記我這題作過,我是有多弱TAT(就算作過我也不會證實= =)

因而果斷orz zyf神犇

http://zyfzyf.is-programmer.com/posts/89618.html

這裏有兩道看起來十分類似的題目:

查錯  &&  拓撲編號

咱們對比一下這兩道題目:

查錯要求:求一個拓撲序,使得字典序儘量小。

拓撲編號要求:求一個拓撲序,使得1儘可能往前,在此狀況下,2儘可能往前。一次類推。

須要注意,這兩個要求確定是不一樣的。

例如在拓撲編號中 4 1 2 3 5是比 3 1 4 2 5 (固然不必定合法)優的,由於2的位置比較靠前。

咱們分別來敘述這兩個問題的解法:

查錯 只要按正常的拓撲排序,只不過把queue改爲priority_queue便可。正確性顯然。

拓撲編號 咱們反向拓撲排序,每次取出出度爲0的最大的節點,標號,而後用它去更新其餘點的出度。

這個算法的證實我是在這裏看到的:算法證實

這裏再口胡一下:

不妨認爲咱們這樣獲得的不是最優解,那麼令這樣獲得的序列爲a,而後最優解是b。

咱們從後往前開始找到第一位兩個序列不一樣的一位設爲k,那麼a[k]!=b[k],且a[k]>b[k]。(由a的構造方式可知)(先假設這個k存在,再證出矛盾)

再設a[k]出現的b的p位置,即b[p]=a[k]。再設b[p] b[p+1]……b[k]這個子序列爲C。

那麼b[p]必定不是C中的最小元素,由於有b[k]<b[p]=a[k]。

而後不妨設b[q]爲C的最小元素。而後咱們把b[p]移到b[k]的位置,獲得序列bb。

若是bb合法的話,那麼咱們就獲得了一個比b優的解,這與b是最優解矛盾。

(由於b[q]的位置前移了一位,咱們要求編號小的儘量靠前)

但bb顯然是合法的。由於在a序列中k以及後面的是合法的,那麼b後面也這麼作必定也是合法的。

因此必定不存在某個k,使得a[k]!=b[k]。也就是說a=b。

因此算法正確性得證。(證法和連接裏有點不同,但我認爲也是正確的)

相關文章
相關標籤/搜索