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