ICPC2023济南站题解(A B D E G I K M)

本场队伍整体实力较强,金牌线为低罚时7题。做出8题可稳金牌,这里是难度前8题的题解。

ICPC 2023 济南站

D:

本场签到。

ll T;
ll n,m;
char s[N];
ll L1,L2,R1,R2;

ll qiu(ll u) {
    ll res = 0;
    while(u) {
        res = max(u%10, res); u /= 10;
    }
    return res;
}

int main() {
	T = read();
	while(T--) {
    	L1 = read(); R1 = read(); L2 = read(); R2 = read();
        ll R = R1+R2;
        ll L = L1+L2;
        if(R-L>10) {cout<<"9\n"; continue;}
        ll ans = 0;
        for(ll i=L;i<=R;i++) ans = max(ans,qiu(i));
        cout<<ans<<endl;
	}
    
    return 0;
}

I:

可以区间排序,那么肯定是贪心地找最大的区间。

正解就是从左到右,遇到 \(a_i\neq i\) 的位置就找最右边第一个小于 \(a_i\) 的位置,将区间排序。

复杂度n方绰绰有余,正确性证明:每次至少能排好两个数的位置,即当前位置 i 和 i+1。可以满足题目 \(\lfloor \frac n2\rfloor\) 的要求。

ll T;
ll n,m;
int a[N];
int L[N],R[N],cnt;

int main() {
	T = read();
	while(T--) {
        cnt = 0;
    	n = read();
        for(int i=1;i<=n;i++) a[i] = read();
        for(int i=1;i<=n;i++) {
            if(a[i]==i) continue;
            for(int j=n;j>i;j--) {
                if(a[j]<a[i]) {
                    L[++cnt] = i; R[cnt] = j;
                    sort(a+i,a+j+1);
                    break;
                }
            }
        }
        cout<<cnt<<endl;
        for(int i=1;i<=cnt;i++) cout<<L[i]<<" "<<R[i]<<endl;
	}
    return 0;
}

A:

感觉官方题解写的有点问题?说下我觉得更简单的思路。

首先题目保证了必定是合法的括号序列,如果不保证的话怎么判断是否合法:依旧是用一个栈,由于没有左右括号区分,我们用贪心的思想,如果栈顶与当前位置括号相同就出栈,否则入栈。如果是有唯一方案,那就是这种方案,方案不唯一的话说明有的位置可以不出栈。

为了表述方便,我们将形如 ([([])]) 连续嵌套的括号序列称为 “小山峰”,注:必须是两种括号交替才算,比如 (()) 认为是两个小山峰,因为可以变成 ()()

本题结论就是,所有合法的序列最多有两个独立的小山峰,一个最外层为方括号另一个为圆括号,如 ([]) [([()])]

证明:

首先两个相邻的小山峰最外层括号不能一致,否则翻转最中间的两个就会出现第二种情况,例:([()]) ([]) 变成([()]( )[])

其次不能有三个小山峰左右排列在一起,由上面第一条知道,第一个和第三个小山峰最外层是一致的。翻转第一个小山峰最右边的括号和第三个小山峰最左边的括号,就会出现第二种情况,例:() [] ([])变成(( [] )[])

然后是一个括号内不能包含多个小山峰或单个符号相同的小山峰:原因也很简单,如果括号内有多个小山峰,必定会有一个小山峰最外层可以翻转,例:单个符号相同( ([]) )变成( )[]( ),包含两个( [()] () )变成( [()] )( )

综上,合法的所有情况就是最多两个小山峰,一左一右。这种情况的判断也很简单,就是满足 i 和 i+1 括号种类相同的位置最多有两个。

int T;
int n,m;
char s[N];

int main() {
	T = read();
	while(T--) {
    	scanf("%s",s+1); n = strlen(s+1);
        for(int i=1;i<=n;i++) {
            if(s[i]==')') s[i] = '(';
            if(s[i]==']') s[i] = '[';
        }
        int er = 0;
        for(int i=1;i<=n-1;i++) {
            if(s[i]==s[i+1]) er++;
        }
        if(er>2) cout<<"No\n";
        else     cout<<"Yes\n";
	}
    return 0;
}

G:

过的人没A多,但我觉得这种典题反而更容易想出来。

这种两两冲突的问题很容易转换成图论,一条边连两个点,两个点只能选一个。复杂点的题目可以用2-sat,这题是普通的二分图染色。

如果有两行的同一列都是1,那么这两行必须让某一行翻转,这就是一种冲突。后来发现不能单纯记录这一种冲突,因为有的关系是要么两行都翻转,要么两行都不翻转。

这种题也好解决,只需要把每行拆成两个点,一个表示“翻转此行”,另一个表示“不翻转此行”,两个点只能选一个,因此给他们连一条线。

对于两行同一列都是1,必须翻转某一行,那就把“翻转A”和“翻转B”连,“不翻A”和“不翻B”连,表示两个点只能翻转一个。

对于两行镜像列分别为1(第i列和第m+1-i列),两行状态必须保持一致,那就把“翻转A”和“不翻B”连,“不翻A”和“翻转B”连。

如果某列(加上镜像列)超过3个1,或者最中间的列超过2个1,不合法。无法对整个图进行二分图染色,也不合法。

方案数就是2的连通块个数次方,比较经典的计数。

int T;
int n,m;
char s[N];
vector <int> d[N];
vector <int> e[N];
int vis[N];
int ans;

int DFS(int u) {
    FOR() {
        int v = e[u][i];
        if(vis[v]==vis[u]) return 0;
        if(vis[v]) continue;
        vis[v] = (vis[u]==1)?2:1;
        if(!DFS(v)) return 0;
    }
    return 1;
}

int main() {
	T = read();
	while(T--) {
    	n = read(); m = read();
        for(int i=1;i<=m;i++) d[i].clear();
        for(int i=1;i<=2*n;i++) e[i].clear(), vis[i] = 0;
        ans = 1;
        for(int i=1;i<=n;i++) {
            scanf("%s",s+1);
            for(int j=1;j<=m;j++) if(s[j]=='1') d[j].push_back(i);
        }
        if(m&1) if(d[m/2+1].size()>1) { cout<<0<<endl; continue; }
        bool duo = 0;
        for(int i=1;i<=n;i++) {
            e[i].push_back(i+n);
            e[i+n].push_back(i);
        }
        for(int i=1;i<=m/2;i++) {
            if(d[i].size()+d[m+1-i].size()>2) {duo = 1; break;}
            if(d[i].size()+d[m+1-i].size()<2) continue;
            if(d[i].size()==1) {
                int u = d[i][0], v = d[m+1-i][0];
                if(u==v) continue;
                e[u].push_back(v+n);
                e[v+n].push_back(u);
                e[v].push_back(u+n);
                e[u+n].push_back(v);
            }
            else {
                int u,v;
                if(d[i].size()) u = d[i][0], v = d[i][1];
                else u = d[m+1-i][0], v = d[m+1-i][1];
                e[u].push_back(v);
                e[v].push_back(u);
                e[u+n].push_back(v+n);
                e[v+n].push_back(u+n);
            }
        }
        if(duo) { cout<<0<<endl; continue; }
        for(int i=1;i<=2*n;i++) {
            if(vis[i]) continue;
            vis[i] = 1;
            if(DFS(i)) (ans *= 2) %= p;
            else ans = 0;
        }
        cout<<ans<<endl;
	}
    return 0;
}

K:

先把 \(a_i\) 变成 \(a_i-i\)

将某一个区间全部变成同一个数的最优方案,就是所有数往中间凑,容易发现往中位数凑是最优的。

维护中位数的数据结构,可以用对顶堆(顾名思义,一听就懂),由于这题涉及到删除,可以用两个multiset。

由于此题的性质:如果大区间可以满足要求,那么小区间一定也满足要求,因此从左到右的每一个L,其最远位置R也是向右递增的。这就是做出此题的第二个关键点,用双指针。

R向右移动就是加入一个值,L向右移动就是删除一个值,用两个multiset一个记录较小一半,一个记录较大一半,使所有数相同的总步数为较大一半的和减去较小一半的和。复杂度O(nlogn)。

维护二顶堆时为了防止越界,可以先往左堆压入一个负无穷,右堆压入一个正无穷,由于中位数游离在两堆之外,因此需要按照总个数的奇偶分类讨论。

ll T;
ll n,m,k;
ll ans,a[N];
multiset <ll> s1,s2;
ll zuo,you,zhong;
bool jiou = 0;

inline ll read() {
    ll sum = 0, ff = 1; char c = getchar();
    while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
    while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
    return sum * ff;
}

void chushihua() {
    s1.clear(); s2.clear();
    s1.insert(-inf); s2.insert(inf);
    zuo = you = zhong = 0;
    jiou = 0;
}

inline void add(ll x) {
    if(!jiou) {
        ll A = *(--s1.end());
        ll B = *s2.begin();
        if(A<=x && x<=B) { zhong = x; }
        else if(A>x) {
            s1.erase(s1.find(A));
            s1.insert(x);
            zuo += x-A;
            zhong = A;
        }
        else if(B<x) {
            s2.erase(s2.find(B));
            s2.insert(x);
            you += x-B;
            zhong = B;
        }
    }
    else {
        if(x>=zhong) {
            s1.insert(zhong); zuo += zhong;
            s2.insert(x); you += x;
        }
        else {
            s2.insert(zhong); you += zhong;
            s1.insert(x); zuo += x;
        }
        zhong = 0;
    }
    jiou ^= 1;
}

inline void del(ll x) {
    ll A = *(--s1.end());
    ll B = *s2.begin();
    if(!jiou) {
        if(A>=x) {
            s1.erase(s1.find(x)); zuo -= x;
            s2.erase(s2.find(B)); you -= B;
            zhong = B;
        }
        else {
            s2.erase(s2.find(x)); you -= x;
            s1.erase(s1.find(A)); zuo -= A;
            zhong = A;
        }
    }
    else {
        if(zhong==x) {}
        else if(x>zhong) {
            s2.erase(s2.find(x));
            s2.insert(zhong);
            you += zhong-x;
        }
        else {
            s1.erase(s1.find(x));
            s1.insert(zhong);
            zuo += zhong-x;
        }
        zhong = 0;
    }
    jiou ^= 1;
}

int main() {
	T = read();
	while(T--) {
        chushihua();
    	n = read(); k = read();
    	for(ll i=1;i<=n;i++) a[i] = read()-i;
        a[n+1] = inf;
    	ll now = 1;
        ans = 1; 
        add(a[1]); jiou = 1;
        for(ll i=1;i<=n;i++) {
            while(you-zuo<=k) { ans = max(ans,now-i+1); add(a[++now]); }
            del(a[i]);
        }
        cout<<ans<<endl;
	}
    return 0;
}

M:

银牌计算几何题。

有两种做法,都需要极角排序。这里提供一个以O为极点,从向量OP开始逆时针排序的板子(注:只排角度不排长度):

double operator ^ (D A,D B) { return A.x*B.y - B.x*A.y; } //叉积

D O = {0,0}, P = {-1,0};
inline bool cmp(D A,D B) {   //极角排序 (OP向量逆时针)
    if(((P-O)^(A-O))==0 && (P.x-O.x)*(A.x-O.x)>0) return 1;
    if(((P-O)^(B-O))==0 && (P.x-O.x)*(B.x-O.x)>0) return 0;
    if((((P-O)^(A-O))>0) != (((P-O)^(B-O))>0)) return ((P-O)^(A-O)) > ((P-O)^(B-O));
    return ((A-O) ^ (B-O)) > 0;
}

此题第一种解法是枚举凸包内部的点作为极点,排序后发现如果凸包上某一条边满足题意,需要这个小三角形内没有其他点,也就是凸包上这两个点极角排序顺序相邻。

第二种解法是枚举凸包上的边,看看内部哪些点和这条边组成的三角形中没有其他点。这是一个经典的二维偏序问题。先按第一个点排序,记录每个点的排名,保持这些排名不变再按第二个点排序。

满足题意的点特征很好推,这里不方便讲,自己画画就知道了。

第一种解法代码:

ll T;
ll n,m;
ll a[N];
struct D{
    ll x,y;
}d[N];
inline bool cmp(D A,D B) { return (A.x==B.x) ? (A.y<B.y) : (A.x<B.x); }
struct D1{
    ll x,y,id;
}d1[N];
D1 O,P;
D1 operator - (D1 A,D1 B) { return {A.x-B.x,A.y-B.y,0}; }
ll operator ^ (D1 A,D1 B) { return A.x*B.y - B.x*A.y; }
inline bool cmp_vec(D1 A,D1 B) {
    if(((P-O)^(A-O))==0 && (P.x-O.x)*(A.x-O.x)>0) return 1;
    if(((P-O)^(B-O))==0 && (P.x-O.x)*(B.x-O.x)>0) return 0;
    if((((P-O)^(A-O))>0) != (((P-O)^(B-O))>0)) return ((P-O)^(A-O)) > ((P-O)^(B-O));
    return ((A-O) ^ (B-O)) > 0;
}
ll st[N],cnt;
ll vis[N];

ll cha(D aa,D bb,D cc) {
    D A = {bb.x-aa.x,bb.y-aa.y};
    D B = {cc.x-bb.x,cc.y-bb.y};
    return A.x*B.y - A.y*B.x;
}

void TUBAO() {
    st[1] = 1; st[2] = 2; cnt = 2;
    for(ll i=3;i<=n;i++) {
        while(cnt>1 && cha(d[st[cnt-1]], d[st[cnt]], d[i])<=0) cnt--;
        st[++cnt] = i;
    }
    st[++cnt] = n-1;
    for(ll i=n-2;i>=1;i--) {
        while(cnt>1 && cha(d[st[cnt-1]], d[st[cnt]], d[i])<=0) cnt--;
        st[++cnt] = i;
    }
    --cnt;
}

int main() {
	n = read();
    for(ll i=1;i<=n;i++) {
        d[i].x = read(); d[i].y = read();
    }
    sort(d+1,d+n+1,cmp);
    TUBAO();
    for(ll i=1;i<=cnt;i++) vis[st[i]] = 1;
    ll ans = 1;
    ll tot = 0;
    for(ll u=1;u<=n;u++) {
        if(vis[u]) continue;
        tot = 0;
        for(int i=1;i<=n;i++) if(i!=u) d1[++tot] = {d[i].x,d[i].y,vis[i]};
        O = {d[u].x,d[u].y,0}; P = {O.x-1,O.y,0};
        sort(d1+1,d1+tot+1,cmp_vec);
        if(d1[1].id && d1[tot].id) ans++;
        for(int i=1;i<tot;i++) if(d1[i].id && d1[i+1].id) ans++;
    }
    cout<<ans;
    return 0;
}

第二种解法代码(相同函数省去):

ll T;
ll n,m;
ll a[N];
struct D{
    ll x,y;
}d[N];

ll st[N],cnt;
ll vis[N];
ll hou[N];

int main() {
	n = read();
    for(ll i=1;i<=n;i++) {
        d[i].x = read(); d[i].y = read();
    }
    sort(d+1,d+n+1,cmp);
    TUBAO();
    for(ll i=1;i<=cnt;i++) vis[st[i]] = 1;

    ll ans = 1;
    ll tot = 0;
    for(ll j=1;j<=n;j++) if(!vis[j]) d1[++tot] = {d[j].x,d[j].y,0};
    if(tot==0) { cout<<ans; return 0; }
    for(ll i=1;i<=cnt;i++) {


        O = {d[st[i]].x,d[st[i]].y,0}; P = {d[st[i+1]].x,d[st[i+1]].y,0};
        sort(d1+1,d1+tot+1,cmp_vec);
        for(ll j=1;j<=tot;j++) d1[j].id = j;

        O = {d[st[i+1]].x,d[st[i+1]].y,0}; P = {d[st[i]].x,d[st[i]].y,0};
        sort(d1+1,d1+tot+1,cmp_vec);
        hou[tot+1] = tot+1;
        for(ll j=tot;j>=1;j--) hou[j] = min(hou[j+1],d1[j].id);

        for(ll j=1;j<=tot;j++) {
            if(hou[j+1]>d1[j].id) ans++;
        }
        
    }
    cout<<ans;
    return 0;
}

E:

1e5的数据,n方的二分图匹配求法都求不出来,发现边数较少,所以只能是网络流了。

这题dinic的复杂度不太会证,估计这题很多队没过的原因也是因为超时,CF的评测机dinic是可以跑过的,所以就只讲个思路当做参考了。

考虑跑完二分图匹配后残余网络的意义:从源点出发已经无法找到一条增广路到底汇点,因为所有的路已经被割了。

我们只需加一条边,使得这张残余图产生一条增广路即可。

正解:DFS查找从源点出发可以到达多少点(而且是二分图左边的点),以及有多少点可以到达汇点(而且是二分图右边的点),乘起来就是答案,思路还是比较简单的,稍微知道残余网络长什么样就能想出来(虽然这题想出来不代表评测机跑得过)。

这份代码勉强跑得进3s:

int T;
int n,m,s,t;
int ans[2];
int vis[N],dep[N];
int head[N],cur[N],cnt=1;
struct E{
    int to,nxt,cap;
}e[qwq];
queue <int> q;

void chushihua() {
    for(int i=s;i<=t;i++) head[i] = cur[i] = 0;
    cnt = 1;
    ans[0] = ans[1] = 0;
}

inline void add(int u,int v,int w) {
    e[++cnt] = {v,head[u],w}; head[u] = cnt;
    e[++cnt] = {u,head[v],0}; head[v] = cnt;
}

bool SPFA() {
    for(int i=s;i<=t;i++) dep[i] = inf, vis[i] = 0, cur[i] = head[i];
    q.push(s); dep[s] = 0;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        vis[u] = 0;
        for(int i=head[u]; i; i=e[i].nxt) {
            int v = e[i].to;
            if(dep[v]>dep[u]+1 && e[i].cap) {
                dep[v]=dep[u]+1;
                if(vis[v]) continue;
                q.push(v);
                vis[v] = 1;
            }
        }
    }
    return dep[t] != inf;
}

int DFS(int u,int flow) {
    int res = 0, f;
    if(u==t || !flow) return flow;
    for(int i=cur[u]; i; i=e[i].nxt) {
        cur[u] = i;
        int v = e[i].to;
        if(e[i].cap && (dep[v]==dep[u]+1)) {
            f = DFS(v,min(flow-res,e[i].cap));
            if(f) {
                res += f;
                e[i].cap -= f;
                e[i^1].cap += f;
                if(res==flow) break;
            }
        }
    }
    return res;
}

void DFS1(int u,int cl) {
    ans[cl] += cl?(u<=n):(u>n);
    vis[u] = 1;
    for(int i=head[u]; i; i=e[i].nxt) {
        int v = e[i].to;
        if(vis[v] || e[i].cap!=cl) continue;
        DFS1(v,cl);
    }
}

int main() {
    int x,y;
	T = read();
	while(T--) {
        chushihua();
    	n = read(); m = read(); s = 0; t = 2*n+1;
        for(int i=1;i<=n;i++) add(s,i,1), add(i+n,t,1);
        for(int i=1;i<=m;i++) {
            x = read(); y = read();
            add(x,y+n,1);
        }
    	while(SPFA()) DFS(s,inf);
        for(int i=s;i<=t;i++) vis[i] = 0;
        DFS1(s,1);
        DFS1(t,0);
        cout<<(ll)(ans[0]-1)*(ll)(ans[1]-1)<<endl;
	}
    return 0;
}

B:

树形背包,但是人尽皆知是n^2,怎么优化。

根号分类讨论:

对于k小于根号n的情况,直接树形背包跑,时空复杂度均为 O(nk):上限为k的树形背包复杂度证明

对于k大于根号n的情况,我们思考会发现合法的情况并不多,这棵树切割的次数也非常少,我们还是用同样的转移方程,但是空间上用unordered_map来代替第二维,只记录方案数不为0的情况。

这样的情况总共有多少种,我们设大小为siz的子树里切了i个大小为k的块,那么根据题意可以推出:剩余的块大小一定为 \((siz-i*k)\mod (k+1)\),因此一个 i 对应一个剩余块儿状态,每棵子树最多有根号个状态,和 k 小于根号 n 时的理论复杂度是一样的。树为一条链时复杂度拉满。

但是由于第二种情况用了unordered_map,为了平衡复杂度,我让根号的取值更大一些,让第一种情况充分跑满时间和空间(我是把根号值设到了660)。稍微改一下就卡过去了。

第二种情况代码的细节需要再多说几句:

1:因为我们只需要记录答案不为0的状态,所以出现答案为0的访问我们要立即删除,否则会越滚越多(即使没有赋值,只访问也是会往map里加入键值对的)。

2:STL比较吃内存,记得用完垃圾及时回收,map.clear()即可。

int T;
int n,K;
int f[N][666],siz[N],g[666];
unordered_map <int,int> F[N],G;
vector <int> e[N];

inline int read() {
    int sum = 0, ff = 1; char c = getchar();
    while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
    while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
    return sum * ff;
}

void chushihua() {
    for(int i=1;i<=n;i++) e[i].clear();
    int sq = 660;
    if(K<=sq) for(int i=1;i<=n;i++) for(int j=0;j<=K+1;j++) f[i][j] = 0;
    else      F[1].clear();
}

void DFS1(int u,int fa) {
    siz[u] = 1;
    f[u][1] = 1;
    FOR() {
        int v = e[u][i];
        if(v==fa) continue;
        DFS1(v,u);
        for(int j=0;j<=K+1;j++) g[j] = 0;   // 由于这个背包是必须强制选,所以之前的状态需要删除,我用临时数组g来实现转移。
        for(int j=0;j<=min(K,siz[v]);j++) {
            for(int k=min(K+1-j,siz[u]);k>=1;k--)
                (g[j+k] += (ll)((ll)f[v][j] * (ll)f[u][k]) %p) %= p;
        }
        for(int j=0;j<=K+1;j++) f[u][j] = g[j];
        siz[u] += siz[v];
    }
    f[u][0] = (f[u][K] + f[u][K+1]) %p;
}

void DFS2(int u,int fa) {
    siz[u] = 1;
    F[u][1] = 1;
    FOR() {
        int v = e[u][i];
        if(v==fa) continue;
        DFS2(v,u);
        G.clear();
        for(auto j : F[v]) {
            for(auto k : F[u]) {
                if(j.first+k.first>K+1) continue;
                (G[j.first+k.first] += (ll)((ll)j.second * (ll)k.second) %p) %= p;
            }
        }
        F[u].clear();
        for(auto j : G) F[u][j.first] = j.second;
        siz[u] += siz[v];
    }
    F[u][0] = (F[u][K] + F[u][K+1]) %p;
    if(F[u][K]==0) F[u].erase(K);
    if(F[u][K+1]==0) F[u].erase(K+1);   // 这三个点没有赋值,但是访问了,需要判断下是否是空键值对。
    if(F[u][0]==0) F[u].erase(0);
    for(int v : e[u]) if(v!=fa) F[v].clear();
}

int main() {
    int x,y;
	T = read();
	while(T--) {
        chushihua();
        n = read(); K = read();
        int sq = 660;
        for(int i=1;i<n;i++) {
            x = read(); y = read();
            e[x].push_back(y);
            e[y].push_back(x);
        }
        if(K<=sq) {
            DFS1(1,1);
            cout<<(f[1][0]%p+p)%p<<endl;
        }
        else {
            DFS2(1,1);
            cout<<(F[1][0]%p+p)%p<<endl;
        }
	}
    return 0;
}

后记:充满遗憾的散伙场

和我组队的是两位学长,有一位即将毕业了,所以今年是我们队伍最后一年,明年需要重组。

UESTC_404,我们调侃自己是老银牌队,因为之前所有的比赛都是银牌,银牌首、中、尾都拿过。但这最后一场比赛,却是以铜牌告终。

赛季前,我们学校发生了闻名贴吧的 “电子科技大学22只雄性事件”,所以我们中文名选用了 “UESTC_三只雄性”。

热身赛做完四道题之后,我们发现我们队正好在菜狗上面,非常兴奋,赶紧拍照留念,虽然后面我们两队中间又夹了几个队,不过有幸拍到了那一刻。我们觉得是个好兆头。

正式赛的第一个小时我们顺利做完了 D G I 三道题,A题稍微卡了下也在半小时之后做出来了,殊不知这就是整场所有的戏份了。

之后我看了E题,随口提了句 “跑完网络流在残余网络上搞搞试试?”,但是发现基本没人做就放弃了,先看 K 和 M。

我和队友 K 题的思路有分歧,我是想先差分,然后题意转化为左右移动小石子使得区间全为0,队友觉得应该先二分,然后数据结构维护下。后来想到对顶堆可以维护中位数,我就上去写,我的脑子就是从这时候开始混沌的。

二分答案(序列长度),再套个STL,本来就是俩log,我一直觉得不放心。一开始我选的是priority_queue,因为涉及到删除,我用一个数组来表示每个数的剩余数量,发现很难写,调了很久样例都没过,浪费了好多时间,队友说换成set不就好写了,对啊,我就赶紧又写了份multiset。

这段时间是比较难熬的,因为两个log自己都觉得过不去,却还是硬着头皮写完了,(我们一直在想怎么优化掉STL的这个log来着),期间唯一欣慰点的是队友会做 M 了。

一交,果然TLE了,悬着的心终于死了。

赶紧换队友写M,这时候已经知道自己与金牌无缘了,想着这场能过六题就知足了。

队友改了很久的排序顺序(应该是二维偏序那里,“正着不对?换倒着试试”),终于过样例了,然后一交,WA。

然后我们队就死气沉沉了,我还一直在想 K 题怎么避免用STL。终于队友发现根本不用二分,用双指针就行了,匆匆忙改了改,交上去变成了RE,这下给整不会了。

我一直以为是我们的双指针越界的问题,因为数组开的够大,而且set里有无穷大,应该不会是STL越界的问题。直到前几天补题(揭伤疤的感觉),才知道multiset在erase时,如果是迭代器,则只会删除一个值,如果是数值,会把所有位置全删掉。我们这题就是因为删的数多了所以RE掉了。

也不能怪时运,其实是自己STL基础没打好,唉。

至于M题,其实到现在还不知道WA的原因。

我们队就一直被这两题卡着卡到比赛结束,最恶心的一场。

我们学校的另外两个队,WiFi和花火,都是8题金牌(WiFi其实也是崩了,只做了8题)。我们却只有4题,4题首位,甚至领先第二80分钟。

大一刚打ICPC时,我一直期望的是学长带飞自己,所以一直是漫不经心。这场打完之后,我突然觉得作为学弟更应该负起责任,因为明年,我还有机会参加比赛,但学长的成绩就只能止步于此了。

这场ICPC成为了我们几个人参赛以来最大的遗憾,因为要散伙了,难免会有些气愤,感到时运不济之类的(比如回来的飞机上我突然觉得E怎么这么简单,之前做过好几道处理残余网络的题)。

虽然今年与EC无缘,但是我才大二,明年还有机会。需要有把自己作为队伍主力的心态,即使不是主力,也要以此为目标去努力,以后才能至少不像这场散伙赛一样让人感到遗憾。

posted @ 2024-02-04 00:29  maple276  阅读(1037)  评论(1编辑  收藏  举报