• 2016-2017 National Taiwan University World Final Team Selection Contest


    2016-2017 National Taiwan University World Final Team Selection Contest

    A. Hacker Cups and Balls

    题目描述:给定一个长度为(n)的序列(a), 有(m)次操作,每次操作选择一个区间([L, R]), 将区间内的数从小到大排序,或从大到小排序,问最终序列中间的数是什么。

    solution
    二分答案(mid),将小于(mid)的数变成(-1),大于(mid)的数变成(1),等于(mid)的数为0,每次操作相当于询问某个区间内(-1)(1)的个数,然后将区间重新染色,这个可以用线段树维护。最终如果中间的数是(0),则二分的值就是答案,如果是(-1)说明答案偏大,否则答案偏小。

    时间复杂度: (O(nlog^2n))

    B. Bored Dreamoon

    题目描述:有(n)个人,他们的高度(h_i)两两不同,现在他们要排队,有四个要求:

    1. 如果(A, B)不在同一行,则排在前面的人比后面的人矮。
    2. 如果(A, B)在同一行,则排在右边的人比左边的人矮。
    3. 任意两行的人数的差不能大于(1)
    4. 前面行的人数要大于等于后面行的人数。
      定义(A)(B)的右前方:如果(A, B)不在同一行且在(A)右边(同一行)的人数不多于在(B)右边(同一行)的人数,或者(A, B)在同一行,且(A)(B)的右边。

    给定两两之间右前方的关系,问第一行人数的最小值,或者无解。

    solution
    (f[i])表示(i)这个人至少排在从右数起第(f[i])个位置,根据题目给的条件可以列出一些不等式,然后用差分约束求解。
    但不太清楚为什么这样可以保证条件3,4.

    时间复杂度:(O(n^2))

    C. Crazy Dreamoon

    题目描述:有一个(2000 imes 2000)的网格图,上面有一些线段,问这些线段穿过了多少个格子。

    solution
    枚举(x)坐标,根据线段定位(y)坐标,染色,最后统计答案。

    时间复杂度:(O(2000n))

    D. Forest Game

    题目描述:给定一棵树大小为(n),你现在要将树上的点全部删掉,每次随机选择一个点,得分为这个点所在的树的大小,然后把这个点以及它所连的边删掉,求总得分的期望值乘(n!)

    solution
    考虑每个点对总得分的贡献,考虑两个点(u, v),当选择(u)删掉时,(v)能对(u)的得分作出贡献,当且仅当(u)(v)的路径上,(u)是第一个被删掉的点,这个概率为(路径长度+1)的倒数,这个概率等于(v)(u)的期望贡献,因此问题转化为求每种路径长度有多少条,总得分期望值就等于路径条数/(路径长度+1)的和。这个问题可以用点分治+(FFT)解决。

    时间复杂度:(O(nlog^2n))

    code

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int mod=int(1e9)+7;
    const int maxn=int(1e6)+100;
    
    int n;
    int deep[maxn], size[maxn];
    int fa[maxn], maxs[maxn];
    bool ban[maxn];
    vector<int> pp, p;
    vector<int> out[maxn];
    int cnt[maxn];
    LL ans[maxn], tmpans[maxn];
    LL inv[maxn];
    
    namespace FFT
    {
    	typedef complex<double> cplxd;
    	const double PI=acos(-1);
    
    	int n;
    	int rev[maxn];
    	cplxd ca[maxn], cb[maxn];
    
    	void FFT_init()
    	{
    		for (int i=0; i<n; ++i) rev[i]=0;
    		for (int i=0; i<n; ++i)
    			for (int j=0; 1<<j<n; ++j)
    				rev[i]=(rev[i]<<1) | (i>>j & 1);
    	}
    
    	void FFT(cplxd *a, int type)
    	{
    		for (int i=0; i<n; ++i)
    			if (i<rev[i]) swap(a[i], a[rev[i]]);
    		for (int i=2; i<=n; i<<=1)
    		{
    			cplxd wn(cos(2*PI/i), sin(type*2*PI/i));
    			for (int j=0; j<n; j+=i)
    			{
    				cplxd w(1,0);
    				for (int k=0; k<i>>1; ++k, w*=wn)
    				{
    					cplxd tmp=a[j+k];
    					a[j+k]=a[j+k]+w*a[j+k+(i>>1)];
    					a[j+k+(i>>1)]=tmp-w*a[j+k+(i>>1)];
    				}
    			}
    		}
    	}
    
    	void solve(int *a, int *b, LL *c, int nn)
    	{
    		nn<<=1;
    		n=1;
    		while (n<nn) n<<=1;
    		FFT_init();
    		for (int i=0; i<nn; ++i)
    			ca[i]=cplxd(a[i], 0), cb[i]=cplxd(b[i], 0);
    
    		for (int i=nn; i<n; ++i)
    			ca[i]=cplxd(0, 0), cb[i]=cplxd(0, 0);
    
    		FFT(ca, 1); FFT(cb, 1);
    		for (int i=0; i<n; ++i) ca[i]=ca[i]*cb[i];
    		FFT(ca, -1);
    		for (int i=0; i<n; ++i) c[i]=LL(ca[i].real()/n+0.5)%mod;
    	}
    }
    
    void read()
    {
    	scanf("%d", &n);
    	for (int i=1; i<n; ++i)
    	{
    		int x, y;
    		scanf("%d%d", &x, &y);
    		out[x].push_back(y);
    		out[y].push_back(x);
    	}
    }
    void dfs2(int cur, int _fa, int deep, int &maxdeep)
    {
    	maxdeep=max(maxdeep, deep);
    	++cnt[deep];
    	for (auto &to:out[cur])
    		if (to!=_fa && !ban[to]) dfs2(to, cur, deep+1, maxdeep);
    }
    int findroot(int cur, int _fa, int &total)
    {
    	int root=0;
    	size[cur]=1;
    	maxs[cur]=0;
    	for (auto &to:out[cur])
    		if (to!=_fa && !ban[to])
    		{
    			int nid=findroot(to, cur, total);
    			if (root==0 || maxs[root]>maxs[nid]) root=nid;
    			size[cur]+=size[to];
    			maxs[cur]=max(maxs[cur], size[to]);
    		}
    	maxs[cur]=max(maxs[cur], total-size[cur]);
    	if (root==0 || maxs[root]>maxs[cur]) root=cur;
    	return root;
    }
    void dfs1(int root)
    {
    	int maxdeep=0;
    	dfs2(root, 0, 0, maxdeep);
    	FFT::solve(cnt, cnt, tmpans, maxdeep+1);
    	for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]+tmpans[i])%mod;
    	for (int i=0; i<=maxdeep; ++i) cnt[i]=0;
    	ban[root]=true;
    	for (auto &to:out[root])
    		if (!ban[to])
    		{
    			maxdeep=0;
    			dfs2(to, 0, 1, maxdeep);
    			FFT::solve(cnt, cnt, tmpans, maxdeep+1);
    			for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]-tmpans[i]+mod)%mod;
    			for (int i=0; i<=maxdeep; ++i) cnt[i]=0;
    		}
    	for (auto &to:out[root])
    		if (!ban[to]) { size[root]=size[to]; dfs1(findroot(to, 0, size[root])); }
    }
    void solve()
    { 
    	dfs1(findroot(1, 0, n));
    	LL answer=0;
    	inv[1]=1;
    	for (int i=2; i<=n; ++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    	for (int i=0; i<n; ++i) answer=(answer+ans[i]*inv[i+1]%mod)%mod;
    	for (int i=1; i<=n; ++i) answer=answer*i%mod;
    	printf("%lld
    ", answer);
    }
    int main()
    {	
    	read();
    	solve();
    	return 0;
    }
    

    E. Lines Game

    题目描述:给定一个序列(p_i),在二维平面上将((0, i))((1, p_i))相连,在给定一个费用(c_i),表示将((0, i), (1, p_i))这条线段删掉的费用,在删掉一条线段的时候,与这条线段相交的线段都会被删掉,但不需要费用,问最少需要多少花费才能把所有线段删掉。

    solution
    将一条线段用一个点((i, p_i))表示,则最终的方案一定是上升的,并且以相邻两个点作为对角的矩形内部没有点。设(f[i])表示(i)一定要选,且(i)左下的点已经被删掉的费用。cdq分治,左右两边按纵坐标从小到大排序,分别维护两个按横坐标排的单调栈,左边的栈是用来维护能作为决策的点,右边是维护对应横坐标能在左边选择的高度范围,然后用线段树维护dp值。

    时间复杂度:(O(nlog^2n))

    code

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int maxn=int(1e5)+100;
    const int inf=0x7fffffff;
    
    struct mes
    {
    	int x, y, v;
    };
    struct data
    {
    	int value, cost, num;
    };
    
    int n;
    data a[maxn];
    int f[maxn], q[maxn], qq[maxn];
    int g[maxn];
    int tree[maxn*4];
    mes dat;
    
    void read()
    {
    	scanf("%d", &n);
    	for (int i=1; i<=n; ++i) scanf("%d", &a[i].value);
    	for (int i=1; i<=n; ++i) scanf("%d", &a[i].cost);
    	for (int i=1; i<=n; ++i) a[i].num=i;
    }
    bool cmp0(data b, data c)
    {
    	return b.value<c.value;
    }
    bool cmp1(data b, data c)
    {
    	return b.num<c.num;
    }
    void build(int cur, int L, int R)
    {
    	tree[cur]=inf;
    	if (L==R) return; 
    	int mid=(L+R)>>1;
    	build(cur<<1, L, mid);
    	build(cur<<1 | 1, mid+1, R);
    }
    void updata(int cur, int L, int R)
    {
    	if (dat.x>R || dat.y<L) return;
    	if (dat.x<=L && R<=dat.y)
    	{
    		tree[cur]=dat.v;
    		return;
    	}
    	int mid=(L+R)>>1;
    	updata(cur<<1, L, mid);
    	updata(cur<<1 | 1, mid+1, R);
    	tree[cur]=min(tree[cur<<1], tree[cur<<1 | 1]);
    }
    int ask(int cur, int L, int R)
    {
    	if (dat.x>R || dat.y<L) return inf;
    	if (dat.x<=L && R<=dat.y) return tree[cur];
    	int mid=(L+R)>>1;
    	return min(ask(cur<<1, L, mid), ask(cur<<1 | 1, mid+1, R));
    }
    void cdq(int L, int R)
    {
    	if (L>=R)
    	{
    		if (f[L]<inf) f[L]+=a[L].cost;
    		return;
    	}
    	int mid=(L+R)>>1;
    	cdq(L, mid);
    	sort(a+L, a+mid+1, cmp0);
    	sort(a+mid+1, a+R+1, cmp0);
    	int head=1, tail=0;
    	int hh=1, tt=0;
    	for (int i=mid+1, j=L; i<=R; ++i)
    	{
    		while (j<=mid && a[j].value<a[i].value)
    		{
    			dat.v=inf;
    			while (head<=tail && a[q[tail]].num<a[j].num)
    			{
    				dat.x=dat.y=a[q[tail]].value; 
    				updata(1, 0, n);
    				--tail;
    			}
    			q[++tail]=j;
    			dat.x=dat.y=a[j].value; dat.v=f[a[j].num];
    			updata(1, 0, n);
    			++j;
    		}
    		while (hh<=tt && a[qq[tt]].num>a[i].num) --tt;
    		qq[++tt]=i;
    		dat.x=a[qq[tt-1]].value; dat.y=a[i].value;
    		if (dat.x<=dat.y) f[a[i].num]=min(f[a[i].num], ask(1, 0, n));
    	}
    	dat.v=inf;
    	while (tail)
    	{
    		dat.x=dat.y=a[q[tail]].value;
    		updata(1, 0, n);
    		--tail;
    	}
    	sort(a+mid+1, a+R+1, cmp1);
    	cdq(mid+1, R);
    }
    void solve()
    {
    	f[0]=0;
    	++n;
    	a[n].value=a[n].num=n;
    	for (int i=1; i<=n; ++i) f[i]=inf;
    	build(1, 0, n);
    	cdq(0, n);
    	printf("%d
    ", f[n]);
    }
    int main()
    {
    	read();
    	solve();
    	return 0;
    }
    

    F. Lonely Dreamoon 2

    题目描述:给定(n)个数,将这(n)个数重新排列,使得相邻两个数的差的绝对值的最小值最大。输出方案。

    solution
    从小到大排序,结论是放在奇数位的数是连续的一段,因此只要找出连续的一段数,头和尾的值相差最小,按顺序放入奇数位,剩下的数从段尾+1开始,循环插入偶数位。

    时间复杂度:(O(n))

    G. Dreamoon and NightMarket

    题目描述:有(n)个数,找出所有子集中,和为第(k)小的子集的和。

    solution
    从小到大排序,二分答案,用一个队列记住当前的有效状态,一个状态包括当前的集合的和以及当前集合选择的数的最大编号。每次从队列里面取出一个状态,然后添加一个数,这个数的编号要大于最大编号,而且加上那个数的和要小于等于二分的答案,看总共有多少个集合的和小于等于二分的答案。

    时间复杂度:(O(nlogn))

    H. Split Game

    题目描述:给定一个在第一象限的简单多边形,用一条穿过原点的直线切割多边形,问最多能切成多少个区域。

    solution
    将点按极角排序。假设现在直线经过点(i), 考虑八种情况。

    对于情况2,4,6,8,区域没有变化,
    对于情况1,当直线再偏一点时区域加一,对于情况4,区域加一。
    对于情况3,区域减一,对于情况7,当直线再偏一点时,区域减一。

    根据不同情况区域的数量变化,求出最大值。

    时间复杂度:(O(nlogn))

    I. Tree Game

    题目描述:给定一棵树,开始时所有边都是白色,每次操作选择两个叶子节点,这两个叶子节点之间的路径要全是白色,然后将路径全涂成黑色,重复操作,直到无法选择为止,问最少要多少次操作。

    solution
    贪心,每棵子树只会向它的父亲传递两个叶子节点,对于一棵子树,
    如果有多于两个儿子子树为(1),则多出来的子树要两两匹配,
    如果有多于一个儿子子树为(2),则多出来的子树要两两匹配,
    如果进行了前两个操作之后,还有两个子树为(1)的儿子,一个子树为(2)的儿子,则用一个(1)匹配一个(2)
    最后想父亲传递的叶子节点数位剩下的叶子节点数与(2)取最小值。

    时间复杂度:(O(n))

    J. Zero Game

    题目描述:给定一个(01)串,有一种操作:将某个位置的数移到某个位置。有若干个询问,每次询问为一个数,问用这么多次操作得到的串的最长(0)子串为多长。

    solution
    如果移动的数字为(1),则相当于将(1)直接移除,如果是(0),则相当于从某个地方移一个(0)过来。
    先将连续的相同数字合并。设(s1[i])(1)的个数前缀和,(s0[i])(0)的个数前缀和。枚举右端(i),要求出(j)使得((s0[i]-s0[j])-(s1[i]-s1[j]))最大,因为这相当于用((s1[i]-s1[j]))次操作获得了((s0[i]-s0[j]))个零,剩下的操作数直接从另外的地方移(0)过来。这里可以用单调队列维护。

    时间复杂度:(O(Qn))

  • 相关阅读:
    MVC的异步,Entity Framework的异步,ADO.NET的异步,
    IE10、IE11 无法写入Cookie
    jQuery在IE浏览器上的html()报错 return !noData || noData !== true && elem.getAttribute("classid") === noData;
    站点、应用程序和虚拟目录详细介绍
    javascript中的一些核心知识点以及需要注意的地方
    $.browser.msie 报错 null
    【转】虚拟机安装Ubuntu的上网设置(有线网络和无线网络)
    Linux编程(获取系统时间)
    【Java】解析xml
    【Java】多线程
  • 原文地址:https://www.cnblogs.com/GerynOhenz/p/10074266.html
Copyright © 2020-2023  润新知