20210718 noip19
阅读原文时间:2023年07月09日阅读:1

考场

去年考过这场,心态直接爆炸

T1 一眼

T2 当初是我讲的,基本都记得(flag)

T3 只记得是树形 DP,但觉得 rush 完前两题后用大量时间应该能搞出来

结果 T2 写了好久,还写假了。。。

T3 没时间写了,直接骗分。交题的时候发现链上的分写的不对,没改完

res

rk3 100+20+1

T2 仅有的分是本来要用来对拍的暴力,而且还是靠数据水拿的

rk1 ycx 100+85+100

rk2 wcr 100+2+37

u

考场写了 std 做法,和去年不一样

考场代码

const int N = 2e3+5;
int n,q;

LL ans,a[N][N],b[N][N];

signed main() {
    // freopen("a.in","r",stdin);
    // freopen("a.out","w",stdout);
    read(n,q);
    while( q-- ) {
        int r,c,l,s; read(r,c,l,s);
        a[r][c] += s, a[r+l][c+l] -= s;
        b[r+l][c] -= s, b[r+l][c+l] += s;
    }
    For(i,1,n) For(j,1,n) a[i][j] += a[i-1][j-1];
    For(i,1,n) For(j,1,n) a[i][j] += a[i-1][j];
    For(i,1,n) For(j,1,n) b[i][j] += b[i-1][j]+b[i][j-1]-b[i-1][j-1];
//    For(i,1,n) {
//        For(j,1,n) printf("%d ",a[i][j]+b[i][j]);
//        putchar(10);
//    }
    For(i,1,n) For(j,1,n) ans ^= a[i][j] + b[i][j];
    write(ans);
    return iocl();
} 

v

题意就没理解。暴力、手玩都挂了

一个移除的白球对答案的贡献是不一样的,如果它在第一次操作移除,那就是 \(\frac 1n\),第二次则是 \(\frac1{n(n-1)}\),依次类推。因此不能算出最大移除白球总数后乘 \(\frac{n!}{(n-k)!}\)

code(与去年有所不同)

const int N = 35, S = 5e7;
int n,m,s;

double ans,f[S+1];
map<int,double> g;

int cut(int x,int i) { return ((x&(~((1<<(i+1))-1)))>>1) | (x&((1<<i)-1)); }
bool get(int x,int i) { return x & (1<<i); }

double dfs(int i,int s) {
    if( i > m ) return 0;
    if( s <= S && f[s] != -1 ) return f[s];
    else if( g.count(s) ) return g[s];
    double res = 0;
    int l = (n-i+1)/2;
    for(int j = 0; j < l; ++j)
        res += 2 * max(dfs(i+1,cut(s,j))+get(s,j),
                       dfs(i+1,cut(s,n-i-j))+get(s,n-i-j));
    if( (n-i+1) & 1 ) res += dfs(i+1,cut(s,l))+get(s,l);
    res /= n-i+1;
    (s<=S ? f[s] : g[s]) = res;
    return res;
}

signed main() {
    read(n,m);
    For(i,1,n) s |= (readc()=='W')<<(i-1);
    s |= 1<<n;
    For(i,0,S) f[i] = -1;
    printf("%.8lf",dfs(1,s));
    return 0;
} 

w

当初就没理解清楚。

这题对边有限制,而树形 DP 都是基于节点的,因此要进行类似树剖维护边权的边转点操作,\(f[u][1/0]\) 保存的是 \(u\) 到父亲的边翻/不翻的最小奇数度点数和最少翻转边数。转移时 \(x,y\) 也保存的是如果有从 \(u\) 的子树中伸到 \(u\) 的路径,两两配对后剩下1/0条的答案。

计算 \(f[u][0/1]\) 时根据 \(w(fa,u)\) 的情况分类讨论,决策有:

f[u][0]: 从子树伸上来的边在 \(u\) 处结束;子树伸上来的边都两两配对了

f[u][1]: 从子树伸上来的边继续向上伸;新开一条路径

code

const int N = 1e5+5;
int n,mm=1,head[N],to[N*2],w[N*2],nxt[N*2];

const PII inf = MP(1e9,1e9);
PII f[N][2];

PII operator + (const PII &x,const PII &y) { return MP(x.fi+y.fi,x.se+y.se); }

void adde(int x,int y,int z) {
    to[++mm] = y, w[mm] = z, nxt[mm] = head[x], head[x] = mm;
    to[++mm] = x, w[mm] = z, nxt[mm] = head[y], head[y] = mm;
}
void dfs(int u,int fa,int op) {
    PII x = inf, y = MP(0,0);
    for(int i = head[u], v; i; i = nxt[i]) if( (v=to[i]) != fa ) {
        dfs(v,u,w[i]);
        PII xx = min(x+f[v][0],y+f[v][1]), yy = min(x+f[v][1],y+f[v][0]);
        x = xx, y = yy;
    }
    if( op == 1 ) f[u][0] = inf;
    else f[u][0] = min(x+MP(1,0),y);
    if( !op ) f[u][1] = inf;
    else f[u][1] = min(x+MP(0,1),y+MP(1,1));
}

signed main() {
    read(n);
    for(int i = 1; i < n; ++i) {
        int x,y,a,b; read(x,y,a,b);
        adde(x,y,b!=2 ? a!=b : 2);
    }
    dfs(1,0,2);
    printf("%d %d",f[1][0].fi/2,f[1][0].se);
    return iocl();
} 

反思

原题大赛,考得很烂

首先是心态上,开场发现是原题就心态不稳。T1 记忆犹新,快速写完 T1 并拍上是对的,T2 大概记得做法而没做出来就是因为太浮躁,没好好读题,而有些细节不记得也没读出来。调 T2 的时候一直在尝试想起原来的做法而不是仔细找现在的 bug。以后遇到原题大概回忆一下,就当新题来做。

其次在题目上,T3 这种 DP 一直是我的弱项,改题时发现当时就没理解透彻,导致这次还是不会做。对于这种当时没有理解的题,要记下来,事后多看、多想。写过的博客不要写完就完了,以后每次模拟赛前看一篇。

最后是策略上,在 20min 拍上 T1 后,绝大部分时间都用来写 T2,导致 T3 没有思考且只写了很少时间,写的时候也在想 T2,最终挂掉了链上的分。以后遇到这种很久调不出来的题就先跳了,不要因小失大。

实话实说,考原题的话我自认是最强的。但从上次考出一道并暴毙之后没有采取什么措施,反而 ycx 做得很好,这次就体现出来了。

好好反思,不要重蹈覆辙