前言:
个人的 I C P C ICPC ICPC 第一站,还是值得记录一下的(虽然咕到现在才记录),总体而言体验很不错,比赛兼旅游。这套题总体印象就是树树树图,作为队里数据结构兼图论选手,这次也确实写了大部分题目(明示下次几乎爆零),但也因为我属于慢热型,题目都是中后期连着开,前期猛跪,罚时炸裂。
现场赛最先看了 D D D 题,想了十来分钟没思路,跟 z z y zzy zzy 换了 K K K 题,然后 x b x xbx xbx 喂了 B B B 题,很快跟榜过了。然后就是卡了半小时 K K K 题才出思路,交了喜提 T L E TLE TLE,卡到大概 2 h 2h 2h 的时候才过( v e c t o r vector vector 存图被卡了,邻接矩阵比较快),此时从铁牌区上升到铜牌区。然后 z z y zzy zzy 成功过掉 D D D,接着与 x b x xbx xbx 证明了下 H H H 也成功上机 1 A 1A 1A,这时候大约 3.5 h 3.5h 3.5h,此时应该是银末。不久 z z y zzy zzy 与 x b x xbx xbx 讨论完 E E E 题,上机敲,封榜后不久成功过了(然而应该是险过,没有用基数排序,小常过了),这时候已经稳在银牌区,看了下再过一题有机会进金末。最后就全力开 F F F 题,队友帮忙验了下式子、看取模,剩 20 m i n s 20mins 20mins 的 2 2 2 发过了,按封榜前的榜估摸着直接上升到 r k 20 rk20 rk20 左右,然后就是快乐吃饭时间。
前 2 h 2h 2h 仅过两道签到,后面一路从铁牌区上去也是挺刺激的。滚榜看着被滚出金牌区,虽然遗憾但最后 r k 37 rk37 rk37 也挺满意了,本来小目标便是拿个银。最后很意外拿到 E C EC EC 名额,也是后面才知道的,那就是我个人爆零的另一段故事了(什么时候会记录呢?
这次借着计算思维课程,把这场补题计划作为课程作业,也一举两得。队友 x b x xbx xbx 一起选修了这门课,队长 z z y zzy zzy 擅长数学、数论(但没选上这门课), C C C 题组合数学我俩实在推不来,就留着吧。
重现赛链接:https://ac.nowcoder.com/acm/contest/4370
两人完成的,所以码风会不同,以下为参考题解(长文预警):
A - Mr. Panda and Dominoes
问题简述:
平面上有 n n n 个黑格子,求有多少个外围是黑色格子的 1 : 2 1:2 1:2 或 2 : 1 2:1 2:1 的 矩形。
所需知识点:
离散化,树状数组。
问题形式化描述:
二维平面 [ 1 , 1 0 9 ] × [ 1 , 1 0 9 ] [1, 10^9] × [1, 10^9] [1,109]×[1,109] 上给定 n n n 个黑点,求有多少矩形 ( x , y ) , ( x + L − 1 , y + W − 1 ) (x, y),(x + L - 1, y + W - 1) (x,y),(x+L−1,y+W−1)(两点表示),满足 L : W = 1 : 2 L:W = 1:2 L:W=1:2 或 L : W = 2 : 1 L:W = 2:1 L:W=2:1 且矩形四条边上全为黑点。
解题思路:
将 x y xy xy 坐标旋转一下就可以把 2 : 1 2:1 2:1 的情况转为 1 : 2 1:2 1:2 的情况,接下来仅考虑 1 : 2 1:2 1:2 的情况。
由于外围黑格子需要连续,先离散化,预处理出每个黑格子向四个方向最长延伸距离是多少。考虑枚举每一条斜率为 2 2 2 的直线,在该直线上枚举矩形的右上角,统计有多少个黑格子可作为该矩形的左下角,满足条件是这个点的最上延长能够到达枚举的右上角。
问题就转化为一个数轴上(就是枚举的直线上),有若干个原有区间(矩形左下角的点),需要查询区间 [ l , r ] [ l ,r ] [l,r] 中有多少个原有区间满足左端点在 [ l , r ] [ l, r ] [l,r] 内, 右端点在 r r r 右面。 我们可以把询问区间拆成两个询问区间 [ 1 , r ] [ 1, r ] [1,r] 和 [ 1 , l − 1 ] [ 1, l -1] [1,l−1] 的差,扫描线加树状数组维护即可。
总时间复杂度为 O ( T ∗ n l o g n ) O(T * nlogn) O(T∗nlogn),需要注意常数优化。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+9;
const ll base = 2e9;
struct Point{
int x,y;
bool ri;
}p[N];
struct Option{
int l , r;
int ty;
bool operator < (const Option& b)const{
return l == b.l ? ty < b.ty : l < b.l;
}
};
bool test;
int n;
int tr[2*N];
int X[2*N];
vector<Point> vec[2*N];
vector< Option > opt;
vector<int> use;
struct HashTable{
const static int md = 2000003;
struct Edge{
ll key; int val, nxt;
} edge[2*N];
int head[md], rub[2*N], cnt, top;
void init(){
while(top) head[rub[top--]] = 0;
cnt = 0;
}
void add(ll key, int val){
int u = abs(key % md);
edge[++cnt] = {
key, val, head[u]}; head[u] = cnt;
rub[++top] = u;
}
int fin(ll key){
int u = abs(key % md);
for(int i = head[u]; i; i = edge[i].nxt){
if(edge[i].key == key) return edge[i].val;
}
return 0;
}
void ins(ll key, int val){
if(!fin(key)) add(key, val);
}
} le,ri,up,dw,mp;
bool cmpx(Point &a,Point &b){
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
bool cmpy(Point &a,Point &b){
return a.y == b.y ? a.x < b.x : a.y < b.y;
}
//树状数组
void add(int x,int v,int mx){
for(int i = x;i;i-=i&-i) tr[i] += v;
}
void clear(int x,int mx){
for(int i = x;i<=mx;i+=i&-i) tr[i] = 0;
}
int query(int x,int mx){
int res =0 ;
for(int i = x;i<=mx;i += i&-i) res += tr[i];
return res;
}
//处理每个黑格点向四个方向的最长延长多少
void init(){
le.init() ; ri.init();
up.init() ; dw.init();
mp.init();
sort(p+1,p+1+n,cmpx);
for(int i = 1;i<=n;++i){
if( int tmp = le.fin((p[i].x-1) * base + p[i].y) ) le.ins(p[i].x*base+p[i].y, tmp + 1);
else le.ins(p[i].x * base + p[i].y, 1);
}
for(int i = n;i>=1;--i){
if( int tmp = ri.fin((p[i].x+1)*base+p[i].y) ) ri.ins(p[i].x*base+p[i].y, tmp + 1);
else ri.ins(p[i].x*base+p[i].y, 1);
}
sort(p+1,p+1+n,cmpy);
for(int i = 1;i<=n;++i){
if( int tmp = dw.fin(p[i].x*base+p[i].y-1) ) dw.ins(p[i].x*base+p[i].y, tmp + 1);
else dw.ins(p[i].x*base+p[i].y, 1);
}
for(int i = n;i>=1;--i){
if( int tmp = up.fin(p[i].x*base+p[i].y+1) ) up.ins(p[i].x*base+p[i].y, tmp + 1);
else up.ins(p[i].x*base+p[i].y, 1);
}
}
//计算每一条直线的答案
ll work(vector<Point> &vec){
opt.clear();
int cnt = 0;
for(auto it : vec){
ll x = it.x , y = it.y;
//假如该点是某个格点的右上角
if( it.ri ){
int nle = le.fin(x*base+y); int ndw = dw.fin(x*base+y);
Option tem;
int len = min(nle,ndw/2);
tem.l = x - len -1 ; tem.r = x ; tem.ty = -1;
opt.push_back(tem);
tem.l = x - 1; tem.r = x; tem.ty = 1;
opt.push_back(tem);
}
else{
int nri = ri.fin((x+1)*base+y+1); int nup = up.fin((x+1)*base+y+1);
int len = min(nri,nup/2);
Option tem;
tem.l = x; tem.r = x + len; tem.ty = -2;
opt.push_back(tem);
} // 左下角
}
for(auto& it : opt){
X[++cnt] = it.l; X[++cnt] = it.r;
}
sort(X+1,X+1+cnt);
cnt = unique(X+1,X+1+cnt) - X - 1;
for(auto& it : opt){
it.l = lower_bound(X+1,X+1+cnt,it.l) - X;
it.r = lower_bound(X+1,X+1+cnt,it.r) - X;
}
ll res = 0;
use.clear();
sort(opt.begin(),opt.end());
for(auto it : opt){
int l = it.l , r = it.r , ty = it.ty;
if( ty == -2){
add(it.r,1,cnt);
use.push_back(it.r);
}
else{
int tem = query(it.r,cnt);
res += ty*tem;
}
}
for(auto it : use){
clear(it,cnt);
}
return res;
}
ll solve(){
ll res = 0; int tot = 0;
init();
for(int i = 1;i<=n;++i){
Point tem = p[i];
tem.ri = 1;
ll val = tem.y - 2ll * tem.x;
if(!mp.fin(val)) mp.ins(val, ++tot);
vec[mp.fin(val)].push_back(tem);
--tem.x; --tem.y;
tem.ri = 0;
val = tem.y - 2ll * tem.x;
if(!mp.fin(val)) mp.ins(val, ++tot);
vec[mp.fin(val)].push_back(tem);
}
for(int i = 1; i <= tot; ++i) res += work(vec[i]), vec[i].clear();
return res;
}
const int maxs = 1e6 + 5;
char buf[maxs], *p1 = buf, *p2 = buf;
inline char fr(){
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, maxs, stdin)) == p1 ? -1 : *p1++;
}
#define gc fr()
inline void read(int &x){
char ch; while(!isdigit(ch = gc)); x = ch ^ 48;
while(isdigit(ch = gc)) x = x * 10 + (ch ^ 48);
}
int main(){
int T; read(T);
for(int cas = 1;cas <= T; ++cas){
read(n);
ll ans = 0; int x, y;
for(int i = 1;i<=n;++i){
read(p[i].x), read(p[i].y);
}
ans += solve();
//旋转xy坐标来统计2:1的情况
for(int i = 1;i<=n;++i){
int tx = p[i].x,ty = p[i].y;
p[i].x = -ty;
p[i].y = tx;
}
ans += solve();
printf("Case #%d: %lld\n",cas,ans);
}
return 0;
}
B - Prefix Code
问题简述:
给定 n n n 个长度最多为 10 10 10 的数字字符串,问是否存在某个字符串是另一个字符串的前缀。
所需知识点:
字典树。
问题形式化描述:
给定 n n n 个字符串 s i ( ∣ s i ∣ ≤ 10 , Σ = { 0 , 1 , ⋯ , 9 } ) s_i(|s_i| \leq 10, \Sigma = \{0, 1, \cdots, 9\}) si(∣si∣≤10,Σ={ 0,1,⋯,9}),求是否存在 i , j ( i ≠ j ) i, j(i \neq j) i,j(i=j),使得 s i ∈ p r e f i x ( s j ) s_i \in prefix(s_j) si∈prefix(sj)。
解题思路:
先对给定的 n n n 个字符串建立字典树,并且对每个字符串的末尾节点的值加 1 1 1,即每一个节点维护有多少个字符串以当前节点为结尾。
对每一个字符串 s i s_i si,在字典树上遍历,假如当前字符串遍历的节点(除了末节点)当中存在值不是 0 0 0 的节点,则 ∃ j ≠ i , s j ∈ p r e f i x ( s i ) \exists j \neq i,~s_j \in prefix(s_i) ∃j=i, sj∈prefix(si);或者末尾节点的值大于 1 1 1, 则 ∃ j ≠ i , s i ∈ p r e f i x ( s j ) \exists j \neq i, s_i \in prefix(s_j) ∃j=i,si∈prefix(sj)。
总时间复杂度为 O ( T ∗ 10 n ) O(T*10n) O(T∗10n)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+9;
string a[N];
struct Node{
int v;
int son[11];
}tr[N*10];
int tot;
void ins(string s){
int n = s.size();
int now = 1;
for(int i = 0;i<n;++i){
int w = s[i] - '0';
if(tr[now].son[w] == 0) tr[now].son[w] = ++tot;
now = tr[now].son[w];
}
tr[now].v++;
}
bool que(string s){
int n = s.size();
int now = 1;
for(int i = 0;i<n-1;++i){
int w = s[i] - '0';
now = tr[now].son[w];
if(tr[now].v) return 0;
}
now = tr[now].son[s[n-1]-'0'];
if(tr[now].v>1) return 0;
return 1;
}
int main(){
int T; cin>>T;
for(int cas = 1;cas<=T;++cas){
tot = 1;
int n; cin>>n;
for(int i = 1;i<=n;++i) cin>>a[i];
for(int i = 1;i<=n;++i) ins(a[i]);
bool ok = 1;
for(int i = 1;i<=n && ok;++i){
if(!que(a[i])) ok = 0;
}
cout<<"Case #"<<cas<<": "<<(ok?"Yes":"No")<<endl;
for(int i = 1;i<=tot;++i){
for(int j = 0;j<=9;++j) tr[i].son[j] = 0;
tr[i].v = 0;
}
}
return 0;
}
C - Maze
D - Spanning Tree Removal
问题简述:
给定 n n n 个顶点的完全图 G G G,每次选择任意生成树,将生成树边删去,问最多能删几次生成树。
所需知识点:
生成树、构造。
问题形式化描述:
给定完全图 G = ( V , E ) G = (V, E) G=(V,E),求最大的 k k k,使得 ∀ i , 1 ≤ i ≤ k , G i = ( V , E i ) \forall i, 1 \leq i \leq k, G_i = (V, E_i) ∀i,1≤i≤k,Gi=(V,Ei) 为 G G G 的生成树,且 ∀ i , j , 1 ≤ i < j ≤ k , G i ⋂ G j = ( V , ∅ ) \forall i, j, 1 \leq i \lt j \leq k, G_i \bigcap G_j = (V, \emptyset) ∀i,j,1≤i<j≤k,Gi⋂Gj=(V,∅)。
解题思路:
由度数易得 a n s ( n ) ans(n) ans(n) 的一个上界为 ⌊ n 2 ⌋ \lfloor \cfrac{n}{2}\rfloor ⌊2n⌋,猜想 a n s ( n ) = ⌊ n 2 ⌋ ans(n) = \lfloor \cfrac{n}{2}\rfloor ans(n)=⌊2n⌋。
使用数学归纳法证明当 n = 2 k ( k ≥ 1 ) n = 2k(k \geq 1) n=2k(k≥1), a n s ( n ) = n 2 ans(n) = \cfrac{n}{2}~ ans(n)=2n 。
当 n = 2 n = 2 n=2,显然 a n s ( 2 ) = 1 ans(2) = 1 ans(2)=1, G 1 = ( { 1 , 2 } , { ( 1 , 2 ) } ) G_1 = (\{1, 2\},\{(1, 2)\}) G1=({ 1,2},{ (1,2)})。
假设对 n = 2 k ( k ≥ 1 ) n = 2k(k \geq 1) n=2k(k≥1), a n s ( n ) = k ans(n) = k ans(n)=k,当 n = 2 ( k + 1 ) n = 2(k + 1) n=2(k+1),新增边集 Δ E = { ( u , v ) ∣ 2 k + 1 ≤ u ≤ 2 k + 2 , 1 ≤ v < u } \Delta E = \{(u, v) \mid 2k + 1 \leq u \leq 2k + 2, 1 \leq v \lt u\} ΔE={ (u,v)∣2k+1≤u≤2k+2,1≤v<u}。
对 G i ( i ≤ k ) G_i(i \leq k) Gi(i≤k),都需要额外添加两条边 ( 2 k + 1 , v i ) , ( 2 k + 2 , v j ) (2k + 1, v_i), (2k + 2, v_j) (2k+1,vi),(2k+2,vj),对于新增的 G k + 1 G_{k + 1} Gk+1,需要添加 n − 1 n - 1 n−1 条,令 E k + 1 = { ( 2 k + 1 , v ) ∣ 1 ≤ v ≤ k } ⋃ { ( 2 k + 2 , v ) ∣ k + 1 ≤ v ≤ 2 k + 1 } E_{k + 1} = \{(2k + 1, v) \mid 1 \leq v \leq k\} \bigcup \{(2k + 2, v) \mid k + 1 \leq v \leq 2k + 1 \} Ek+1={ (2k+1,v)∣1≤v≤k}⋃{ (2k+2,v)∣k+1≤v≤2k+1} ,剩余的 { ( 2 k + 1 , v ) ∣ k + 1 ≤ v ≤ 2 k } ⋃ { ( 2 k + 2 , v ) ∣ 1 ≤ v ≤ k } \{(2k + 1, v) \mid k + 1 \leq v \leq 2k\} \bigcup \{(2k + 2, v) \mid 1 \leq v \leq k \} { (2k+1,v)∣k+1≤v≤2k}⋃{ (2k+2,v)∣1≤v≤k} 恰能分成 k k k 组 ( 2 k + 1 , v i ) , ( 2 k + 2 , v j ) (2k + 1, v_i), (2k + 2, v_j) (2k+1,vi),(2k+2,vj) 分配到 G i ( 1 ≤ i ≤ k ) G_i(1 \leq i \leq k) Gi(1≤