山西胡策 #7

A.

题意:给一个有向图无环连通图,求添加一条边X->Y后有向生成树的方案数。(n<=100000)

#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$的入度。

考虑加边,首先考虑上面的做法,那么答案显然是多加了的,而多加的部分就是由一个新环以及环外其他点组成的生成树的方案。由于环上一定存在X->Y这条边,因此我们只需要计算原图Y->X的路径数及其答案即可,即令

$$f(i) = \sum_{Y->i的路径 \\ 且经过的点集为S} \prod_{j>1且j \notin S} d(j)$$

由于原图为拓扑图,因此按照拓扑序dp即可,即

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

最后特判一下Y=1的情况= =

B.

题意:给一棵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太神啦。。

本题需要想到:

1、用dfs序来表示路径覆盖。

2、二分答案

3、整体二分

首先考虑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错。。。

1、我整体二分的时候分到右半区间的第K小没有减去当前的得到的。。因此后面分治的时候就错了= =(因为前半区间的插入并没有到后半区间来= =)

2、插入矩形的时候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。

所以算法正确性得证。(证法和链接里有点不一样,但我认为也是正确的)

 

你可能感兴趣的:(#)