\(\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)\) 的。
另外这题也可以用 \(\rm splay\) 做,但在修改时不能使用 splay()
函数:因为要维护小根堆的性质,双旋可能破坏这一性质,所以只能 rotate()
到根(不过复杂度就很奇怪了。
所以还是得会 \(\text{fhq_treap}\).
代码
\(\text{fhq_treap}\):
#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;
}
\(\rm splay\):
#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' || s<'0')
f |= (s=='-');
while(s>='0' && s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(T x) {
static int writ[50],w_tp=0;
if(x<0) putchar('-'),x=-x;
do writ[++w_tp]=x-x/10*10,x/=10; while(x);
while(putchar(writ[w_tp--]^48),w_tp);
}
#include <algorithm>
using namespace std;
typedef long long ll;
inline ll S(int x) { return (1ll*x*(x+1))>>1; }
const int maxn = 1e5+5;
int r,c,n,rt;
struct fish {
int x,y;
bool operator < (const fish& t) const {
return y<t.y;
}
} f[maxn];
struct node {
int son[2],fa,h,s,la; ll ans;
} t[maxn];
bool dir(int o) { return t[t[o].fa].son[1]==o; }
void add(int o,int f,bool d) {
if(o) t[o].fa=f;
if(f) t[f].son[d]=o;
}
void inc(int o,int k) {
if(!o) return;
t[o].la+=k, t[o].h+=k;
}
void pushDown(int o) {
if(!o || !t[o].la) return;
inc(t[o].son[0],t[o].la);
inc(t[o].son[1],t[o].la);
t[o].la=0;
}
void pushUp(int o) {
if(!o) return;
int lc=t[o].son[0], rc=t[o].son[1];
t[o].s = t[lc].s+t[rc].s+1; t[o].ans=0;
if(lc) t[o].ans = t[lc].ans+S(t[lc].s)*(t[lc].h-t[o].h);
if(rc) t[o].ans += t[rc].ans+S(t[rc].s)*(t[rc].h-t[o].h);
}
void rotate(int o) {
int f=t[o].fa, ff=t[f].fa;
bool d=dir(o), fd=dir(f);
add(t[o].son[d^1],f,d);
add(f,o,d^1), add(o,ff,fd);
pushUp(f), pushUp(o);
}
void pushAll(int o) {
static int stk[maxn],tp;
stk[tp=1] = o;
while(t[o].fa) stk[++tp] = (o=t[o].fa);
while(tp) pushDown(stk[tp--]);
}
void build(int &o,int l,int r) {
if(l>r) return;
o = l+r>>1;
build(t[o].son[0],l,o-1);
build(t[o].son[1],o+1,r);
if(t[o].son[0]) t[t[o].son[0]].fa=o;
if(t[o].son[1]) t[t[o].son[1]].fa=o;
pushUp(o);
}
void printTree(int o) {
if(!o) return;
printf("ID(%d) son(%d %d) %lld %d %d\n",o,t[o].son[0],t[o].son[1],t[o].ans,t[o].s,t[o].h);
pushDown(o);
printTree(t[o].son[0]);
printTree(t[o].son[1]);
}
int main() {
r=read(9), c=read(9), n=read(9);
for(int i=1;i<=n;++i)
f[i].x=read(9), f[i].y=read(9);
sort(f+1,f+n+1); ll ans = S(r)*S(c);
build(rt,1,r); int j=1;
for(int i=1;i<=c;++i) {
inc(rt,1);
while(j<=n && f[j].y==i) {
int o=f[j].x; pushAll(o);
while(t[o].fa) rotate(o);
t[o].h=0; rt=o;
pushUp(o); ++ j;
}
ans -= t[rt].ans+S(t[rt].s)*t[rt].h;
}
print(ans,'\n');
return 0;
}