[Contest on 2021.9.2] 读不懂题啊!

目录

\(\text{Knockout}\)

原题:\(\text{Boomerang Tournament}\)。

题目描述

有 \(n\) 支队伍(\(n\) 是 \(2^k\))进行淘汰赛,已知两两胜负关系。在每种对阵情况中,请计算出第 \(i\) 支队伍的最好名次和最劣名次。

\(1\le T,n\le 16\),\(G_{i,i}=0\),对于所有 \(i\neq j\),都有 \(G_{i,j}\neq G_{j,i}\)。


需要注意名次的计算方式。将所有队伍按胜出场数从大到小排序,比如有两队并列第三,那么 下一种 场数的队伍就应该是第五名。

\(\rm sb\) 博主观察了老久的样例都没看懂…

解法

很玄学的是,我随机化了 \(10^5\) 次就拿到了 \(\rm 100pts\)… 代码戳这。但是我算了算,本质不同的排列是 \(\frac{16!}{2^8\cdot 2^4\cdot 2^2 \cdot 2}\) 的?就很离谱。

令 \(dp_{s,i}\) 是在 \(s\) 的集合中,\(i\) 是否能成为优胜者,其中 \(s\) 一定是 \(2\) 的幂。转移是比较简单的,我主要是分析一下复杂度。

考虑有四个 \(1\) 的集合共有 \(\binom{16}{4}=1820\) 个,有八个 \(1\) 的集合共有 \(\binom{16}{8}=12870\) 个,其余的都比较小了,不妨只分析 \(siz=16,8\) 的循环。

这样大概是 \(\mathcal O((1\cdot 12870\cdot 8^2/2+12870\cdot \binom{8}{4}\cdot 4^2/2)\cdot T)\),还是蛮稳的。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T> 
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9') 
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
} 

template <class T> 
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <vector>
#include <cstring>
#include <iostream>
using namespace std;

const int maxn=20;

int n,g[maxn][maxn];
int dp[1<<16][17];
int mx[maxn],mn[maxn];
vector <int> mask[17];

void init() {
	for(int i=0;i<(1<<16);++i) {
		int t=__builtin_popcount(i);
		if(((t-1)&t)==0)
			mask[t].push_back(i);
	}
}

int lowbit(int x) {
	return x&-x;
}

int main() {
	freopen("knock.in","r",stdin);
	freopen("knock.out","w",stdout);
	int fk=0;
	init();
	for(int T=read(9);T;--T) {
		memset(dp,0,sizeof dp);
		n=read(9);
		for(int i=0;i<n;++i) {
			mx[i]=0,mn[i]=n;
			dp[1<<i][i]=1;
			for(int j=0;j<n;++j)
				g[i][j]=read(9);
		}
		printf("Case #%d:\n",++fk);
		int dep=1;
		for(int siz=2;siz<=n;siz<<=1,++dep)
			for(int uu=0;uu<mask[siz].size();++uu) {
				int U=mask[siz][uu];
				if(U>=(1<<n)) break;
				for(int ss=0;ss<mask[siz>>1].size();++ss) {
					int S=mask[siz>>1][ss];
					if((S&U)!=S) continue;
					int T=U^S;
					if(S>T) continue;
					int s=S;
					while(s) {
						int i=__builtin_ctz(s);
						s-=lowbit(s);
						if(!dp[S][i]) continue;
						int t=T;
						while(t) {
							int j=__builtin_ctz(t);
							t-=lowbit(t);
							if(!dp[T][j]) continue;
							if(g[i][j]) {
								dp[U][i]=1;
								mx[i]=max(mx[i],dep);
								mn[j]=min(mn[j],dep-1);
							}
							else {
								dp[U][j]=1;
								mx[j]=max(mx[j],dep);
								mn[i]=min(mn[i],dep-1);
							}
						}
					}
				}
			}
		--dep;
		for(int i=0;i<n;++i) {
			int a=(mx[i]==dep)?0:(1<<(dep-mx[i]-1));
			int b=(mn[i]>=dep)?0:(1<<(dep-mn[i]-1));
			printf("%d %d\n",a+1,b+1);
		}
	}
	return 0;
}

\(\text{[ZJOI 2012] }\)小蓝的好友

解法

首先转化成一条鱼都没有的矩形有多少个。

考虑添加一条与 \(x\) 轴平行的扫描线,从上往下移,那么扫描线以上的部分可以形成一个柱状图(遇见鱼 \((x,y)\) 后将经过 \(x\) 的柱高度置为 \(0\))。

对于固定的柱状图,我们可以以 \(x\) 为 \(\rm key\),高度为 \(\rm val\) 建立一棵笛卡尔树。对于每个宽为 \(w\),长为 \(h\) 的完整矩形,内部矩形计算公式是 \(\frac{w(w+1)}{2}\cdot h\)(相当于最左边的 \(x\) 与 \(x,x+1,...,x+w-1\) 配对,依此类推)。单次计算是 \(\mathcal O(n)\) 的。

如何优化?考虑每次往下移扫描线,所有柱高度加一,遇到鱼时柱高度变为 \(0\),而且还要维护笛卡尔树。用 \(\rm treap\) 啊!

当某个柱高度变为 \(0\) 时,\(\rm val\) 改变导致堆的条件不满足,所以需要 split() 一下。因为鱼是随机的,所以是 \(\mathcal O(n\log n)\) 的。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T> 
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9') 
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
} 

template <class T> 
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <algorithm>
using namespace std;
typedef long long ll;

const int maxn=100005;

ll S(int n) {
	return 1ll*n*(n+1)/2;
}

int r,c,n;
struct fish {
	int x,y;
	
	bool operator < (const fish &t) const {
		return y<t.y;
	}
} s[maxn];
struct fhq_treap {
	int idx,rt;
	struct node {
		int h,ls,rs,siz,la;
		ll ans;
	} t[maxn];
	
	int NewNode(int H) {
		t[++idx].siz=1;
		t[idx].h=H;
		return idx;
	}
	
	void add(int o,int k) {
		if(!o) return;
		t[o].la+=k;
		t[o].h+=k;
	}
	
	void pushDown(int o) {
		if(!o or !t[o].la)
			return;
		add(t[o].ls,t[o].la);
		add(t[o].rs,t[o].la);
		t[o].la=0;
	}
	
	void pushUp(int o) {
		if(!o) return;
		t[o].ans=0;
		t[o].siz=t[t[o].ls].siz+t[t[o].rs].siz+1;
		if(t[o].ls)
			t[o].ans+=t[t[o].ls].ans+S(t[t[o].ls].siz)*(t[t[o].ls].h-t[o].h);
		if(t[o].rs)
			t[o].ans+=t[t[o].rs].ans+S(t[t[o].rs].siz)*(t[t[o].rs].h-t[o].h);
	}
	
	void split(int o,int k,int &x,int &y) {
		if(!o) x=y=0;
		else {
			pushDown(o);
			if(t[t[o].ls].siz+1<=k) {
				x=o;
				split(t[o].rs,k-t[t[o].ls].siz-1,t[o].rs,y);
			}
			else {
				y=o;
				split(t[o].ls,k,x,t[o].ls);
			}
			pushUp(o);
		}
	}
	
	int merge(int x,int y) {
		if(!x or !y)
			return x|y;
		if(t[x].h<t[y].h) {
			pushDown(x);
			t[x].rs=merge(t[x].rs,y);
			pushUp(x);
			return x;
		}
		else {
			pushDown(y);
			t[y].ls=merge(x,t[y].ls);
			pushUp(y);
			return y;
		}
	}
	
	void build(int n) {
		for(int i=1;i<=n;++i)
			rt=merge(rt,NewNode(0));
	}
} T;

int main() {
	r=read(9),c=read(9),n=read(9);
	for(int i=1;i<=n;++i)
		s[i]=(fish){read(9),read(9)};
	T.build(r);
	sort(s+1,s+n+1);
	ll ans=S(r)*S(c);
	int pos=1;
	for(int i=1;i<=c;++i) {
		T.add(T.rt,1);
		while(pos<=n and s[pos].y==i) {
			int cut=s[pos].x,a,b,c;
			T.split(T.rt,cut-1,a,b);
			T.split(b,1,b,c);
			T.t[b].h=0;
			T.rt=T.merge(T.merge(a,b),c);
			++pos;
		}
		ans-=T.t[T.rt].ans+S(T.t[T.rt].siz)*T.t[T.rt].h;
		// 最后还要计算最下面完整的一块
	}
	print(ans,'\n');
	return 0;
}
上一篇:linux磁盘管理、三剑客之awk语法命令详解


下一篇:机器学习优化器