山西胡策 #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。

因此算法正确性得证。(证法和连接里有点不同,但我认为也是正确的)

相关文章
相关标签/搜索