2019ICPC上海区域赛 补题(12/13)& 总结

本文是对2019年ICPC上海区域赛的赛后补题与总结,详细解析了包括离散化、字典树、并查集、最小生成树等算法在各个问题中的应用,并提供了相应的解题思路和参考代码。文章介绍了作者在比赛中遇到的挑战,如初期慢热,但最终通过团队协作取得满意成绩,获得了EC名额。
摘要由CSDN通过智能技术生成
前言:

个人的 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+L1,y+W1)(两点表示),满足 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,l1] 的差,扫描线加树状数组维护即可。

总时间复杂度为 O ( T ∗ n l o g n ) O(T * nlogn) O(Tnlogn),需要注意常数优化。

参考代码:
#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(si10,Σ={ 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) siprefix(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, sjprefix(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,siprefix(sj)

总时间复杂度为 O ( T ∗ 10 n ) O(T*10n) O(T10n)

参考代码:
#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,1ik,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,1i<jk,GiGj=(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(k1) 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(k1) 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+1u2k+2,1v<u}

G i ( i ≤ k ) G_i(i \leq k) Gi(ik),都需要额外添加两条边 ( 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 n1 条,令 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)1vk}{ (2k+2,v)k+1v2k+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+1v2k}{ (2k+2,v)1vk} 恰能分成 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

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值