断网练习 1

Day 1

一道难题

 一遍过捏

点击查看代码
struct node{
    int v,w;
};

int n,root,m;
vector<node> e[N];
int w[N];
int dp[N];
bool st[N];

void dfs(int u,int fa,int qwq) {
    dp[u] = qwq;
    bool flag = false;
    int sum = 0;
    for (auto it : e[u]) {
        int v = it.v,w = it.w;
        if (v == fa) continue;
        flag = true;
        dfs(v,u,w);
        sum += dp[v];
    }
    if (flag)
        dp[u] = min(dp[u],sum);
}

int main(){
    n = fr(),root = fr();
    int a,b,w;
    m = n - 1;
    while (m --) {
        a = fr(),b = fr(),w = fr();
        e[a].push_back({b,w});
        e[b].push_back({a,w});
    }
    dfs(root,0,inf);
    fw(dp[root]);
    return 0;
}

跳舞的线

 这里要注意的就是说他这个初始化的时候不能把所有第一行和第一列全部初始化为0,还要注意第一行第一列上面有没有障碍物,如果有的话后面的点是到不了的。所以在初始化的时候就这样赋值就可以啦()

 然后就是这个地方要注意一下特判起点是 # 的情况就可以了

点击查看代码
int n,m;
int dp[N][N][2];
//0是竖着,1是横着
bool flag[N][N];

int main(){
    n = fr(),m = fr();
    string s;
    for (int i = 1; i <= n; i ++) {
        cin >> s;
        for (int j = 0; j < m; j ++) {
            if (s[j] == '#')
                flag[i][j + 1] = true;
        }
    }
    memset(dp,-0x3f,sizeof dp);
    dp[1][1][0] = dp[1][1][1] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            if (flag[i][j]) continue;
            if (i == 1 || j == 1) {
                if (i == 1 && j == 1) continue;
                if (i == 1) dp[i][j][1] = dp[i][j - 1][1];
                if (j == 1) dp[i][j][0] = dp[i - 1][j][0];
                continue;
            }
            if (!flag[i - 1][j]) {
                dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j][1] + 1);
            }
            if (!flag[i][j - 1]) {
                dp[i][j][1] = max(dp[i][j - 1][1],dp[i][j - 1][0] + 1);
            }
        }
    }
    int ans = max(dp[n][m][0],dp[n][m][1]);
    memset(dp,0x3f,sizeof dp);
    dp[1][1][0] = dp[1][1][1] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            if (flag[i][j]) continue;
            if (i == 1 || j == 1) {
                if (i == 1 && j == 1) continue;
                if (i == 1) dp[i][j][1] = dp[i][j - 1][1];
                if (j == 1) dp[i][j][0] = dp[i - 1][j][0];
                continue;
            }
            if (!flag[i - 1][j]) {
                dp[i][j][0] = min(dp[i - 1][j][0],dp[i - 1][j][1] + 1);
            }
            if (!flag[i][j - 1]) {
                dp[i][j][1] = min(dp[i][j - 1][1],dp[i][j - 1][0] + 1);
            }
        }
    }
    if (flag[1][1]) {
        wj;
        return 0;
    }
    if (dp[n][m][1] >= inf / 2 && dp[n][m][0] >= inf / 2) {
        wj;
        return 0;
    }
    fw(ans),kg;
    fw(min(dp[n][m][0],dp[n][m][1]));
    return 0;
}

Day 2

排兵布阵

 一遍过捏

点击查看代码
int n,m,s;
int dp[N][M];
int w[N][N];

int main(){
    s = fr(),n = fr(),m = fr();
    for (int i = 1; i <= s; i ++) {
        for (int j = 1; j <= n; j ++) {
            w[j][i] = fr();
        }
    }
    for (int i = 1; i <= n; i ++) {
        sort(w[i] + 1,w[i] + 1 + s);
    }
    memset(dp,-0x3f,sizeof dp);
    memset(dp[0],0,sizeof dp[0]);
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= m; j ++) {
            dp[i][j] = max(dp[i - 1][j],dp[i][j]);
            for (int k = 1; k <= s; k ++) {
                if (j > w[i][k] * 2)
                dp[i][j] = max(dp[i - 1][j - w[i][k] * 2 - 1] + k * i,dp[i][j]);
                else break;
            }
        }
    }
    fw(dp[n][m]);
    return 0;
}

玩具取名

 区间dp,要分区求解,这里的三维dp的前面两维就代表的是区间。

 而这里的flag数组表示的是 j,k 两个字母可以由 i 转化而来

 大的区间可以用小的区间转移,状态转移方程如下:

dp[i][j][k]=(dp[i][k][x]&&dp[k+1][j][y]&&ok[x][y][k])

 意思是当 [i,k] 这个区间可以用 x 来代替并且 [k+1,j] 这个区间可以用 y 来代替,那么 [i,j] 这个区间就可以看成是 xy 如果 xy 可以用 k 代替,那么 [i,j] 就可以用 k 来代替。

点击查看代码
int n = 4;
int w[N];
char c[N];
string s;
bool flag[5][5][5];//j,k => i
bool dp[N][N][5];//k => i,j

int get(char i) {
    if (i == 'W') return 1;
    if (i == 'I') return 2;
    if (i == 'N') return 3;
    if (i == 'G') return 4;
    return 19920929;
}

int main(){
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= w[i]; j ++) {
            scanf("%s",(c + 1));
            flag[i][get(c[1])][get(c[2])] = true;
        }
    }
    cin >> s;
    int len = s.length();
    for (int i = 0; i < len; i ++) {
        dp[i][i][get(s[i])] = true;
    }
    for (int cd = 2; cd <= len; cd ++) {
        for (int l = 0; l < len; l ++) {
            int r = l + cd - 1;
            if (r >= len) break;
            for (int k = l; k < r; k ++) {
                for (int t = 1; t <= 4; t ++) {
                    for (int a = 1; a <= 4; a ++) {
                        for (int b = 1; b <= 4; b ++) {
                            dp[l][r][t] |= flag[t][a][b] & dp[l][k][a] & dp[k + 1][r][b];
                        }
                    }
                }
            }
        }
    }
    bool st = false;
    if (dp[0][len - 1][1]) {
        st = true;
        cout << "W";
    }
    if (dp[0][len - 1][2]) {
        st = true;
        cout << "I";
    }
    if (dp[0][len - 1][3]) {
        st = true;
        cout << "N";
    }
    if (dp[0][len - 1][4]) {
        st = true;
        cout << "G";
    }
    if (!st) wj;
    return 0;
}

Day 3

4084 Barn Painting G

 一遍过捏

点击查看代码
int n,k;
vector<int> e[N];
int col[N];
lwl dp[N][4];

void dfs(int u,int fa) {
    if (col[u]) {
        dp[u][col[u]] = 1;
    } else 
        dp[u][1] = dp[u][2] = dp[u][3] = 1;
    for (auto v : e[u]) {
        if (v == fa) continue;
        dfs(v,u);
        dp[u][1] = (lwl)dp[u][1] * (dp[v][2] + dp[v][3]) % mod;
        dp[u][2] = (lwl)dp[u][2] * (dp[v][1] + dp[v][3]) % mod;
        dp[u][3] = (lwl)dp[u][3] * (dp[v][1] + dp[v][2]) % mod;
    }
}

int main(){
    n = fr(),k = fr();
    int a,b;
    for (int i = 1; i < n; i ++) {
        a = fr(),b = fr();
        e[a].push_back(b);
        e[b].push_back(a);
    }
    while (k --) {
        a = fr(),b = fr();
        col[a] = b;
        dp[a][b] = 1;
    }
    dfs(1,0);
    lwl ans = (lwl)dp[1][1] + dp[1][2] + dp[1][3];
    ans %= mod;
    fw(ans);
    return 0;
}

8903 Bribing Friends G

 这个地方就是要用一些奇怪的贪心,假设已经确定要选一些牛,那么肯定把冰淇淋给那些可以兑换更多钱的牛为优,所以要么都用,要么就是其它都用剩余的都用在这头牛上面。

 然后按照每头牛所需要的冰淇淋的大小顺序从小到大排序,预处理一下到第 i 头牛还剩 j 个冰淇淋的最大值和到第 i 头牛还剩 j 块钱的最大值。然后这里遍历的时候 dp 是从前往后,f 是从后往前

 但是n4有七十五分,我很满足

点击查看代码
struct node{
    int p,c,x;
};

int n,A,B;
node w[N];
int dp[N][N],f[N][N];
//到第i头牛,剩了j个冰淇淋。
//到第i头牛,剩了j块钱
//dp从前往后,f从后往前.

bool cmp(node a,node b) {
    return a.x < b.x;
}

int main(){
    n = fr(),A = fr(),B = fr();
    int ww,yy,hh;
    for (int i = 1; i <= n; i ++) {
        ww = fr(),yy = fr(),hh = fr();
        w[i] = {ww,yy,hh};
    }
    sort(w + 1,w + 1 + n,cmp);
    for (int i = 1; i <= n; i ++) {
        int p = w[i].p,c = w[i].c,x = w[i].x;
        for (int j = 0; j <= B; j ++)
            dp[i][j] = dp[i - 1][j];
        for (int j = 0; j + x * c <= B; j ++)
            dp[i][j + x * c] = max(dp[i][j + x * c],dp[i - 1][j] + p);
    }
    for (int i = n; i; i --) {
        int p = w[i].p,c = w[i].c;
        for (int j = 0; j <= A; j ++)
            f[i][j] = f[i + 1][j];
        for (int j = 0; j + c <= A; j ++) {
            f[i][j + c] = max(f[i][j + c],f[i + 1][j] + p);
        }
    }
    lwl ans = 0;
    for (int i = 1; i <= n; i ++) {
        int p = w[i].p,c = w[i].c,x = w[i].x;
        ans = max(ans,dp[i][B] + f[i + 1][A]);
        ans = max(ans,dp[i - 1][B] + f[i][A]);
        for (int j = 0; j <= min(A,c); j ++) {
            if (x * (c - j) > B) continue;
            ans = max(ans,dp[i - 1][B - x * (c - j)] + f[i + 1][A - j] + p);
        }
    }
    fw(ans);
    return 0;
}

Day 4

5468 回家路线

 这一题一开始写的时候爆搜有八十分我很震惊,爆搜交上去没过的点是 WA 而不是 TLE 我更震惊。还没找到是哪里出问题导致 WA 了,贴了个帖子看看有没有好心人

 wanqitzh找到问题了,dfss里面他有时候会把值给更新掉,所以改了个写法,变成九十五分了,WA了一个点,这个分数我很满意(点头)

 就是当有这种情况的时候,按照数字的顺序遍历边,就会使得到3的时候dp[u]已经变了

  正解(不完全)是根据每个站点出发的时间排一个序,然后从前往后遍历一遍所有车次,处理出到第 i 个站点时间为 j 的烦躁值最小值,然后全部车站遍历完之后再把dp[n][]都遍历一遍,每一次用 ans 在(dp[n][i]+i)里面取一个最小值。具体 dp 方程看代码捏()。

点击查看代码
struct node{
    int v,be,en;
};

struct Node{
    int u,v,be,en;
};

int n,m,A,B,C;
Node w[N * 2];
int dp[N][1005];
int ans = inf;

bool cmp(Node a,Node b) {
    return a.be < b.be;
}

int get(int q,int p) {
    return A * (p - q) * (p - q) + B * (p - q) + C;
}

int main(){
    n = fr(),m = fr(),A = fr(),B = fr(),C = fr();
    int a,b,be,en;
    for (int i = 1; i <= m; i ++) {
        a = fr(),b = fr(),be = fr(),en = fr();
        w[i] = {a,b,be,en};
    }
    sort(w + 1,w + 1 + m,cmp);
    memset(dp,0x3f,sizeof dp);
    dp[1][0] = 0;
    for (int i = 1; i <= m; i ++) {
        a = w[i].u,b = w[i].v,be = w[i].be,en = w[i].en;
        for (int j = 0; j <= be; j ++) {
            dp[b][en] = min(dp[b][en],dp[a][j] + get(j,be));
        }
    }
    for (int i = 0; i <= 1000; i ++) {
        ans = min(dp[n][i] + i,ans);
    }
    fw(ans);
    return 0;
}

Day 5

4933 大师

 一遍过捏

点击查看代码
int n;
int w[N];
int dp[N][V * 2 + 2];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j < i; j ++) {
            int t = w[i] - w[j] + V;
            dp[i][t] = (dp[i][t] + dp[j][t] + 1) % mod;
            ans = (ans + (dp[j][t]) + 1) % mod;
        }
    }
    fw(ans + n);
    return 0;
}

6349 樱花,还有你

 前缀和dp,一开始先写了个暴力然后写的前缀和优化,调了挺久。结果交上去还是wa,后来一看错的点,输出了负数,于是不知道为什么会出现负数但是把 ans 搞成正数就过了。

 建议是下次直接开 longlong

点击查看代码
int n,k;
int dp[N];
int w[N];
int sum[N];

int main(){
    n = fr(),k = fr();
    int Sum = 0;
    for (int i = 1; i <= k; i ++) {
        w[i] = fr();
        Sum += w[i];
    }
    if (Sum < n) {
        wj;
        return 0;
    }
    dp[0] = 1;
    int ans = 0;
    for (int i = 1; i <= k; i ++) {
        sum[0] = 1;
        for (int j = 1; j <= n; j ++) {
            sum[j] = (sum[j - 1] + dp[j]) % mod;
        }
        for (int j = 1; j <= n; j ++) {
            dp[j] = dp[j];
            int t = sum[j - 1] - sum[max(j - w[i],1) - 1];
            t %= mod;
            if (j - w[i] <= 0) t ++;
            dp[j] = (dp[j] + t) % mod;
        }
        ans = (ans + dp[n]) % mod;
    }
    fw((ans + mod) % mod);
    return 0;
}

6040 课后期末考试滑溜滑溜补习班

 单调队列优化。

 一开始 0 分是因为去尾的时候把 while 写成 if 了,于是大寄。以后单调队列优化的时候还是全部都用 while 好了。(虽然一般去头都只要 if 就可以了)

点击查看代码
int n,k,d,x,tp;
int w[N];
int Seed;
int dp[N];

inline int rnd () {
    static const int MOD = 1e9;
    return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}

signed main(){
    n = fr(),k = fr(),d = fr(),x = fr(),tp = fr();
    if (!tp) {
        for (int i = 1; i <= n; i ++) {
            w[i] = fr();
        }
    } else {
        Seed = fr();
        for (int i = 1; i <= n; i ++) {
            w[i] = rnd();
        }
    }
    dp[1] = w[1];
    deque<int> q;
    q.push_front(1);
    for (int i = 2; i <= n; i ++) {
        if (q.size() && i - q.front() > x) {
            q.pop_front();
        }
        dp[i] = dp[q.front()] + k + (i - q.front() - 1) * d + w[i];
        while (q.size()&&dp[q.back()] + (i - q.back()) * d >= dp[i])
            q.pop_back();
        q.push_back(i);
    }
    fw(dp[n]);
    return 0;
}

3961 黄金矿工

一遍过捏

点击查看代码
struct node{
    int x,y,t,v;
};

int n,tim;
node w[N];
int dp[N][T][2];

bool cmp(node a,node b) {
    if (a.x * b.y != a.y * b.x){
        return a.x / a.y < b.x / b.y;
    }
    return a.y < b.y;
}

int main(){
    n = fr(),tim = fr();
    int a,b,t,v;
    for (int i = 1; i <= n; i ++) {
        a = fr(),b = fr(),t = fr(),v = fr();
        w[i] = {a,b,t,v};
    }
    sort(w + 1,w + 1 + n,cmp);
    memset(dp,-0x3f,sizeof dp);
    int ans = -inf;
    dp[0][0][0] = dp[0][0][1] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= tim; j ++) {
            dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j][1]);
            if (j < w[i].t) continue;
            int t = w[i].t,v = w[i].v;
            if (w[i].x * w[i - 1].y != w[i].y * w[i - 1].x){
                dp[i][j][1] = max(dp[i - 1][j - t][0],dp[i - 1][j - t][1]) + v;
            }
            else dp[i][j][1] = dp[i - 1][j - t][1] + v;
            ans = max(ans,dp[i][j][1]);
        }
    }
    fw(ans);
    return 0;
}

6323 ZBRKA

这种排列计数的一个套路是设 f[i] 是将前 i 个数字填入排列的方案数,后面的维度可以按照所求补充,然后考虑新加入一个数对答案产生的贡献。

这里设 f[j][i] 是将前 i 个数字填入排列,形成了 j 个逆序对的方案数。

考虑填第 i 个数,已经有了一个 i1 的排列,现在有 i 个位置可以填数字 i ,注意到 i 填到从后向前数第 j 个空里会产生 (j1) 个逆序对。因此转移为

f[i][j]=k=0min(i1,j)f[i1][jk]

直接做复杂度是 O(nc2) 的,但是注意到转移时转移了 fi1 的一个区间和,因此用前缀和维护转移即可做到 O(1) 转移:

fi,j=gi1,jgi1,ji

其中 gi,j=k=0jfi,k 。当然上述转移需要特殊处理一下 ji<0 的情况。

时间复杂度 O(nc) 。但是爆空间了,因此对 fg 都滚动一下数组即可。

点击查看代码
int n,m;
int dp[M][2],sum[M][2];

int main(){
    n = fr(),m = fr();
    dp[0][1] = sum[0][1] = 1;
    for (int i = 1; i <= m; i ++) {
        sum[i][1] = 1;
    }
    for (int i = 2; i <= n; i ++) {
        dp[0][i & 1] = sum[0][i & 1] = 1;
        for (int j = 1; j <= m; j ++) {
            int w = j - i;
            dp[j][i & 1] = sum[j][1 - (i & 1)];
            if (w > -1)
                dp[j][i & 1] = (dp[j][i & 1] - sum[w][1 - (i & 1)]) % mod;
            sum[j][i & 1] = (sum[j - 1][i & 1] + dp[j][i & 1]) % mod;
        }
    }
    int ans = (dp[m][n & 1] + mod) % mod;
    fw(ans);
    return 0;
}

Day 6

5343 分块

 一遍过捏

点击查看代码
lwl n,b,x;
unordered_map<int,int> w;
set<int> s;
lwl dp[N];
lwl a[105][105];
bool flag[105];

void mul1(lwl ans[],lwl a[],lwl b[][105]) {
    lwl temp[105];
    memset(temp,0,sizeof temp);
    for (int i = 1; i <= 100; i ++) {
        for (int j = 1; j <= 100; j ++) {
            temp[i] = (lwl)(temp[i] + (lwl)a[j] * b[j][i]) % mod;
        }
    }
    memcpy(ans,temp,sizeof temp);
}

void mul2(lwl ans[][105],lwl a[][105],lwl b[][105]) {
    lwl temp[105][105];
    memset(temp,0,sizeof temp);
    for (int i = 1; i <= 100; i ++) {
        for (int j = 1; j <= 100; j ++) {
            for (int k = 1; k <= 100; k ++) {
                temp[i][j] = (temp[i][j] + (lwl)a[i][k] * b[k][j]) % mod;
            }
        }
    }
    memcpy(ans,temp,sizeof temp);
}

int main(){
    n = fr();
    b = fr();
    int x;
    for (int i = 1; i <= b; i ++) {
        x = fr();
        w[x] = true;
    }
    b = fr();
    for (int i = 1; i <= b; i ++) {
        x = fr();
        if (w[x]) {
            s.insert(x);
            flag[x] = true;
        } 
    }
    dp[0] = 1;
    for (int i = 1; i <= 100; i ++){
        a[i][i + 1] = 1;
        if(flag[i])
            a[i][1] = true;
    }
    for (int i = 1; i <= 100; i ++) {
        for (auto w : s) {
            if (i < w) continue;
            dp[i] = (dp[i] + dp[i - w]) % mod;
        }
    }
    if (n <= 100) {
        fw(dp[n]);
        return 0;
    }
    n -= 100;
    for (int i = 1; i <= 50; i ++) {
        swap(dp[i],dp[101 - i]);
    }
    while (n) {
        if (n & 1) mul1(dp,dp,a);
        mul2(a,a,a);
        n >>= 1;
    }
    fw(dp[1]);
    return 0;
}

4677 山区建小学

 看题解做的,没看题解之前基本只写了个输入()

 具体分析基本都在代码里面写了,也比较好懂(),就不再写一遍了。这个题目在断网做的时候脑子里一直想的是区间 dp ,然后没想到怎么通过左右区间那个点来转移,于是寄

点击查看代码
int n,m;
int w[N];
int dp[N][N];//考虑前 i 个村庄,用了 j 个小学的最小距离和
int dis[N][N];//在第 i 个到第 j 个村庄之间只用 1 个小学的最小距离和

int main(){
    n = fr(),m = fr();
    int ans = inf;
    for (int i = 2; i <= n; i ++) {
        w[i] = fr();
        ans = min(ans,w[i]);
        w[i] += w[i - 1];
    }
    if (n == m) {
        cout << 0 << endl;
        return 0;
    } if (n == m + 1) {
        cout << ans << endl;
        return 0;
    }
    // 预处理 dis 数组,感性理解一下,在这个区间的时候就是在最中间那个村庄的距离和最小
    // 具体证明课参考小学奥数题捏
    for (int l = 1; l <= n; l ++) {
        for (int r = l; r <= n; r ++) {
            int mid = (l + r) >> 1;
            for (int k = l; k <= r; k ++) {
                dis[l][r] += abs(w[k] - w[mid]);
            }
        }
    }
    for (int i = 0; i <= n; i ++) {
        for (int j = 0; j <= m; j ++) {
            dp[i][j] = inf;//初始化
        }
    }
    dp[0][0] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= min(m,i); j ++) {
            for (int k = j - 1; k <= i; k ++) {
                dp[i][j] = min(dp[i][j],dp[k][j - 1] + dis[k + 1][i]);
            }
        }
    }
    fw(dp[n][m]);
    return 0;
}

6622 信号传递

 这个题断网的时候dfs写挂了,极其生气。

 直接感性理解吧,麻了。(明天写了那种看得懂再说吧)

 找到问题了, dfs 数组开小了,捏妈

 朴素状压 dp ,这里的 dp 的意义是如果 (i >> j) & 1 == 1,那么这个点已经在前面被选了。然后 cnt 和那个正解的 cnt 的意思是一样的。

 状压的时候是从前往后面转移的。状压的时候就先找到现在有了几个 1 ,然后这就代表了前面有了几个站,再 ++ 就表示现在正在处理的这个状态相较于当前状态多的那一个站在哪一个位置。然后这里的每一个传输的代价都是分开算的,也就是代码里面这个转移过程。

 然后如果 ((i>>k) & 1==1) 的话,就说明这个 k 点在 j 点的前面,如果不是的话就说明在后面,这就对应着题目中所给的两种转移方式捏,最后输出一下全部都排列好的情况就可以了捏。总体来说转移公式是这样的(这里要取 min ,但是写不下了所以就没写)

dp[s+i]=dp[s]+posjs(kcnt[i][j]+cnt[j][i])+posjs(kcnt[j][i]cnt[i][j])

点击查看代码
struct node{
    int t;
    int w[N];
};

int n,m,k;
int w[100005];
int cnt[N][N];
int id[1 << N],dp[1 << N];
int h[1 << N];
int ans = inf;

int main(){
    n = fr(),m = fr(),k = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        w[i] --;
    }
    
    //预处理
    for (int i = 1; i < n; i ++)
        cnt[w[i]][w[i + 1]] ++;
    queue<node> q;
    node be;
    be.t = 0;
    for (int i = 0; i < m; i ++) {
        be.w[i] = 0;
        for (int j = 0; j < m; j ++) {
            if (j == i) continue;
            be.w[i] -= cnt[i][j];
            be.w[i] += k * cnt[j][i];
        }
    }
    q.push(be);
    int M = (1 << m) - 1;
    for (int i = 0; i < m; i ++)
        id[1 << i] = i;
    for (int i = 1; i <= M; i ++) {
        dp[i] = inf;
        h[i] = h[i >> 1] + (i & 1);
    }
    
    while (q.size()) {
        auto u = q.front();
        q.pop();
        
        int p = h[u.t] + 1;
        int udp,ut;
        for (int j = M ^ u.t; j; j -= (j & -j)) {
        // 枚举 u.t 的补集里面的一个点 i
            int i = id[j & (- j)];
            udp = dp[u.t],ut = (u.t | (1 << i));
            udp += p * u.w[i];
            dp[ut] = min(dp[ut],udp);
        }
        
        for (int i = 0; i < m; i ++) {
            if ((u.t >> i) & 1) break;
            node v;
            v.t = (u.t | (1 << i));
            for (int y = M ^ v.t; y; y -= (y & -y)) {
            // 枚举 v.t 的补集里面的一个点 j
                int j = id[y & -y];
                v.w[j] = u.w[j] + (k * cnt[j][i] + cnt[i][j]) + cnt[j][i] - k * cnt[i][j];
            }
            q.push(v);
        }
    }
    fw(dp[M]);
    return 0;
}

Day 7

6649 Grid

 由题意可以知道,第一次进入某一个位置的时候只能从右面或者下面进入。

 然后考虑一种情况,进入某一个格子 [i][j] 之后,可以先向左边移动若干格再回到 [i][j] ,如果这些格子的贡献小于 0 ,那么这个时候就更优,那么 dp[i][j] 就应该加上这个值

 然后 w 直接修改为加上左边若干格格子中的数字之后得到的最小值。然后在计算 dp 数组的时候,由于 w[i][j] 的值可能已经包括了 w[i][j1] 的值,所以要特殊判断一下

点击查看代码
int n,m;
lwl w[N][N];
lwl dp[N][N];
int sum[N][N];

//当第一次进入某一个位置的时候只能从它的右侧或者下侧进入
//(若该位置为(i,r),则只能从它的下侧进入,否则只能从它的右侧进入)

//下面考虑一种情况:
//进入某一格a[i][j]之后,可以先向左移动若干格,之后再回到a[i][j],如果这若
//干个格子中的数字之和小于零,那么dp[i][j]就应该加上它得到一个更优的解。

//设b[i][j]为a[i][j]加上左边若干个格子中的数字之后得到的最小值
//这里其实直接在 a 数组上修改即可,详情见代码 
//b[i][j]=min(b[i][j-1],0)+a[i][j];;

//用得到的b数组计算dp的时候,由于b[i][j]的值可能已经包含b[i][j-1]的值
//(即b[i][j-1]b[i][j?1]小于零的情况),所以特殊判断这种情况即可。

int main(){
    n = fr(),m = fr();
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            w[i][j] = fr();
            sum[i][j] = sum[i][j - 1] + w[i][j];
        }
        //将w改为加上左边若干个格子中的数字之后的最小值
        for (int j = 1; j <= m; j ++)
            if (w[i][j - 1] < 0)
                w[i][j] += w[i][j - 1];
    }
    for (int i = n; i; i --) {
        for (int j = m + 1; j; j --) {
            dp[i][j] = linf;
        }
    }
    for (int i = n; i; i --) {
        for (int j = m; j; j --) {
            if (w[i][j] <= 0)
                dp[i][j] = min(dp[i][j + 1],dp[i + 1][j] + w[i][j]);
            else 
                dp[i][j] = min(dp[i][j + 1],dp[i + 1][j]) + w[i][j];
        }
    }
    lwl ans = linf;
    for (int i = 1; i <= m; i ++) {
        ans = min(ans,dp[1][i]);
    }
    fw(ans);
    return 0;
}

Day 8

6786 GCDs & LCMs

 一遍过捏。数学推导在代码上面写了注释的

点击查看代码
int n;
int w[N];
set<int> t;
map<int,int> h;
map<int,int> cnt;
map<int,lwl> sum;

int find(int x) {
    if (x != h[x])
        h[x] = find(h[x]);
    return h[x];
}

// bj < bi + bj + gcd(bi,bj) < 3bj
// lcm(bi,bj) = k * bj (k -> N* && 1 < k < 3)
// bi + bj + gcd(bi,bj) = lcm(bi,bj) = 2 * bj
// bi + gcd(bi,bj) = bj;
// bj - bi = gcd(bi,bj)
// lcm(bi,bj) = 2 * bj
// lcm(bi,bj) = bj * bi / gcd(bi,bj)
// bi = 2 * gcd(bi,bj);
// bj = 3 * gcd(bi,bj);

int main(){
    n = fr();
    lwl ans = -inf;
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        t.insert(w[i]);
        cnt[w[i]] ++;
    }
    for (auto i : t) {
        h[i] = i;
        sum[i] = (lwl)i * cnt[i];
        ans = max(ans,sum[i]);
    }
    for (auto u : t) {
        if (u & 1) continue;
        int v = u / 2 * 3;
        if (!h[v]) continue;
        sum[find(v)] += sum[find(u)];
        h[find(u)] = h[find(v)];
        ans = max(ans,sum[h[v]]);
    }
    fw(ans);
    return 0;
}

4823 拯救小矮人

 这里的 dp 是上去了 i 个人还有的最大高度。

 考虑:如果矮的不先走,那他以后有可能永远也走不掉了。高的如果先走,他对人梯的贡献就没了。所以可以得出:先走矮的。但如果两个人总长一样,应该把身子长的留下来,他对梯子的贡献更大。

点击查看代码
int n,H;
pii w[N];
int dp[N];

bool cmp(pii a,pii b) {
    if (a.fi + a.se != b.fi + b.se)
        return a.fi + a.se < b.fi + b.se;
    return a.fi < b.fi;
}

int main(){
    n = fr();
    int a,b;
    for (int i = 1; i <= n; i ++) {
        a = fr(),b = fr();
        w[i] = {a,b};
    }
    H = fr();
    sort(w + 1,w + 1 + n,cmp);
    memset(dp,-0x3f,sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i ++) {
        dp[0] += w[i].fi;
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = i; j; j --) {
            if (dp[j - 1] + w[i].se >= H)
                dp[j] = max(dp[j],dp[j - 1] - w[i].fi);
        }
    }
    while (dp[n] < 0) n --;
    fw(n);
    return 0;
}

Day 9

8801 最大数字

 一遍过捏

点击查看代码
lwl n,a,b;
int idx = 0;
int w[25];
lwl ans = -inf;

void dfs(int u,int a,int b,lwl sum) {
    if (u > idx) {
        ans = max(sum,ans);
        return ;
    }
    if (w[u] + a >= 9 && w[u] + 1 - b <= 0) {
        dfs(u + 1,a - 9 + w[u],b,sum * 10 + 9);
        dfs(u + 1,a,b - w[u] - 1,sum * 10 + 9);
    } else if (w[u] + a >= 9) {
        dfs(u + 1,a - 9 + w[u],b,sum * 10 + 9);
    } else if (w[u] + 1 <= b) {
        dfs(u + 1,a,b - w[u] - 1,sum * 10 + 9);
    } else {
        dfs(u + 1,0,b,sum * 10 + w[u] + a);
    }
}

int main(){
    n = fr();
    a = fr(),b = fr();
    while (n) {
        w[++ idx] = n % 10;
        n /= 10;
    }
    for (int i = 1; i <= idx / 2; i ++) {
        swap(w[i],w[idx - i + 1]);
    }
    dfs(1,a,b,0);
    fw(ans);
    return 0;
}

6722 village

 如果图上有奇环,那么这就不是一个二分图。

 然后从链入手分析,如果这张图上面有奇环的话,那么就一定有三元环(原图中奇环的头和尾以及中间的点,头和尾的距离 > k ,中间的点和头(尾)的距离 > k ,所以一定有)

 然后扩展一下,也可以说明如果存在奇环,那么一定存在三元环。然后证明若存在三元环,那么存在包括直径两个端点在内的三元环:

 设 AB 为树的直径, (D,E,F) 为一个三元环。假设三元环上任意一点都不满足(反证法)到 A 的距离不小于 k 且到 B 的距离不小于 k

=> (f+d)+l+b<k ====> (f+d)+c>=k =====> (f+d)+c>=c =====> b+l<c
=> a+l+c>a+l+b+l>=a+b ,所以和 AB 是直径矛盾

 所以三元环上存在一点满足到 A 的距离不小于 k 且到 B 的距离不小于 k 。然后找有没有这样的三元环就可以乐

点击查看代码
int n,k;
vector<node> e[N];
int de[N],fa[N][18],dis[N];
int dist[1005][1005];
vector<int> edge[N];
int col[N];

void dfs(int u,int father) {
    de[u] = de[father] + 1;
    fa[u][0] = father;
    for (int k = 1; k < log2(de[u]); k ++) {
        fa[u][k] = fa[fa[u][k - 1]][k - 1];
    }
    for (auto it : e[u]) {
        int v = it.v;
        if (v == father) continue;
        dis[v] = dis[u] + it.w;
        dfs(v,u);
    }
}

int LCA(int x,int y) {
    if (de[x] < de[y]) swap(x,y);
    
    int dh = de[x] - de[y];
    int kmax = log2(dh);
    for (int k = kmax; k >= 0; k --) {
        if ((dh >> k) & 1)
            x = fa[x][k];
    }
    
    if (x == y) return x;
    
    kmax = log2(de[y]);
    for (int k = kmax; k >= 0; k --) {
        if (fa[x][k] != fa[y][k]) {
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    
    return fa[x][0];
}

bool get(int u,int fa,int color) {
    col[u] = color;
    for (auto v : edge[u]) {
        if (fa == u) continue;
        if (!col[v]) {
            if (!get(v,u,3 - color))
                return false;
        }
        else if (col[v] == col[u]) return false;
    }
    return true;
}

int main(){
    int T;
    T = fr();
    while (T --) {
        n = fr(),k = fr();
        if (n > 1000) {
            wj;
            continue;
        }
        for (int i = 1; i <= n; i ++) {
            dis[i] = 0;
            de[i] = 0;
            e[i].clear();
            col[i] = 0;
            edge[i].clear();
        }
        memset(fa,0,sizeof fa);
        int a,b,w;
        for (int i = 1; i < n; i ++) {
            a = fr(),b = fr(),w = fr();
            e[a].push_back({b,w});
            e[b].push_back({a,w});
        }
        dfs(1,0);
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j < i; j ++) {
                int p = LCA(i,j);
                int t = dis[i] + dis[j] - 2 * dis[p];
                if (t >= k) {
                    edge[i].push_back(j);
                    edge[j].push_back(i);
                }
            }
        }
        bool flag = true;
        for (int i = 1; i <= n; i ++) {
            if (!col[i]) {
                if (!get(i,0,1)) {
                    flag = false;
                    break;
                }
            }
        }
        if (flag) yj;
        else wj;
    }
    return 0;
}

6883 Kronican

 一遍过捏

点击查看代码
int n,k;
int w[N][N];
int dp[1 << N];
// 1 => empty

int main(){
    n = fr(),k = fr();
    if (n == k) {
        wj;
        return 0;
    }
    for (int i = 0; i < n; i ++) {
        for (int j = 0; j < n; j ++) {
            w[i][j] = fr();
        }
    }
    int maxn = (1 << n) - 1;
    memset(dp,0x3f,sizeof dp);
    dp[0] = 0;
    int ans = inf;
    for (int i = 0; i < maxn; i ++) {
        int cnt = 0;
        for (int j = 0; j < n; j ++) {
            if ((i >> j) & 1) cnt ++;
        }
        if (n - cnt <= k) ans = min(ans,dp[i]);
        for (int j = 0; j < n; j ++) {
            if ((i >> j) & 1) continue;
            for (int k = 0; k < n; k ++) {
                if (((i >> k) & 1)) continue;
                if (k == j) continue;
                dp[i | (1 << j)] = min(dp[i | (1 << j)],dp[i] + w[j][k]);
            }
        }
    }
    fw(ans);
    return 0;
}

Day 10

5851 Greedy Pie Eaters P

 区间 dp

dp[i][j] 表示 [i,j] ,已经被吃完的最大的价值。

w[k][i][j] 表示当 k 还未被吃时能吃掉 ki<=l<=k<=r<=j 的最大的价值(合并时拥有最大 w 的一个能吃掉第 k 个派的牛,所以 k 必须在这头牛的 [l,r] 之间,切不能把 [i,j] 之外的派吃掉,这样会影响到后面的更新)。
然后dp方程就来乐:

dp[i][j]=max(dp[i][j],dp[i][k1]+dp[k][j]+w[k][i][j])

点击查看代码
int n,m;​
int dp[N][N]; // [i,j] finish max​
int w[N][N][N]; // k i j​
// eat k && i <= l <= k <= r <= j maxw​​bool cmp(node a,node b) {​
    if (a.l != b.l) return a.l < b.l;​
    return a.r < b.r;​
}​
​
int main(){​
    m = fr(),n = fr();​
    int l,r,ww;​
    for (int i = 1; i <= n; i ++) {​
        ww = fr(),l = fr(),r = fr();​
        for (int j = l; j <= r; j ++) {​
            w[j][l][r] = ww;​
        }​
    }​
    for (int k = 1; k <= m; k ++) {​
        for (int i = k; i; i --) {​
            for (int j = k; j <= m; j ++) {​
                if (i != 1)​
                    w[k][i - 1][j] = max(w[k][i - 1][j],w[k][i][j]);​
                if (j != m)​
                    w[k][i][j + 1] = max(w[k][i][j + 1],w[k][i][j]);​
            }​
        }​
    }​
    for (int i = m; i; i --) {​
        for (int j = i; j <= m; j ++) {​
            for (int k = i; k < j; k ++) {​
                dp[i][j] = max(dp[i][j],dp[i][k] + dp[k + 1][j]);​
            }​
            for (int k = i; k <= j; k ++) {​
                dp[i][j] = max(dp[i][j],dp[i][k - 1] + dp[k + 1][j] + w[k][i][j]);​
            }​
        }​
    }​
    int ans = dp[1][m];​
    fw(ans);​
    return 0;​
}

Day 11

7976 游园会

 xx做的。



点击查看代码
lwl n;
lwl w[N],idx;
lwl f[3] = {1,2,1},p[4] = {0,1,3,4};
lwl h[N];

void init() {
    h[1] = 1;
    for (int i = 2; i <= 64; i ++) {
        h[i] = h[i - 1] * 4 % mod; 
    }
}

int main(){
    int T = fr(),maxn = fr();
    maxn += 19920929;
    init();
    while (T --) {
        n = fr();
        lwl ans = 0,t = 1;
        idx = 0,n ++;
        while (n) {
            w[++ idx] = n % 3;
            n /= 3;
        }
        for (int i = idx; i; i --) {
            ans = (ans +  p[w[i]] * t * h[i]) % mod;
            t = t * f[w[i]] % mod;
        }
        fw(ans);
        ch;
    }
    return 0;
}

8848 1-1 B

 (第三点是x <= y)

点击查看代码
int n,m;
int cnt[2];
lwl f[N],nf[N];
lwl dp[N],w[N];

lwl ksm(lwl a,lwl k) {
	lwl ans = 1;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

void init() {
	f[0] = nf[0] = 1;
	for (int i = 1; i <= n; i ++) {
		f[i] = f[i - 1] * i % mod;
		nf[i] = nf[i - 1] * ksm(i,mod - 2) % mod;
	}
}

int main(){
	n = fr();
	init();
	int a;
	for (int i = 1; i <= n; i ++) {
		a = fr();
		if (a < 0) cnt[0] ++;
		else cnt[1] ++;
	}
	if (cnt[1] == 0) {
		wj;
		return 0;
	}
	if (cnt[0] >= cnt[1]) {
		lwl ans = f[cnt[0] + 1] * nf[cnt[0] + 1 - cnt[1]] % mod;
		ans = ans * nf[cnt[1]] % mod;
		fw(ans);
		return 0;
	}
	m = cnt[1] - cnt[0];
	dp[0] = 1;
	for (int i = 1; i <= n; i ++) {
		w[0] = dp[1],w[m] = dp[m - 1];
		for (int j = 1; j < m; j ++) {
			w[j] = (dp[j - 1] + dp[j + 1]) % mod;
		}
		memcpy(dp,w,sizeof w);
	}
	fw(dp[m]);
	return 0;
}

Day 12

7925 童年

 一开始断网写的时候,本来写的是对的,但是因为没有开 longlong 所以爆零了,然后我就以为我的做法有问题,所以就换成了一个题解的写法,结果看讨论区那个题解的贪心是完全错误的。然后就换成Richard_H的写法写了,结果一开始也没开 longlong ,所以就还是爆零。然后我看他的代码改了个 longlong 就过了,然后定睛一看一开始没开 longlong 的错的点有点熟悉,我就把原来的代码改成了 longlong ,结果过了???

 简直是戏剧性

点击查看代码
int n,ans;
int w[N];
int fa[N],d[N];
pii dp[N];
vector<int> e[N];
vector<int> yz;
priority_queue<pii,vector<pii>,greater<pii> > q;

void dfs(int u,int wu,int need) {
    dp[u].se = w[u];
    int t = need;
    for (auto v : e[u]) {
        dfs(v,wu + w[u],t);
        if (dp[v].se < 0) continue;
        need = max(need,dp[v].fi);
        dp[u].se += dp[v].se;
    }
    if (u == 1) return ;
    if (w[u] < 0) need = max(need,-w[u]);
    else if (dp[u].se < 0) need = 0,dp[u].se = 0;
    else need = max(0,need - w[u]);
    dp[u].fi = need;
    if (fa[u] == 1) {
        q.push({dp[u].fi,dp[u].se});
    }
}

signed main(){
    n = fr(),ans = fr();
    bool flag = false;
    for (int i = 2; i <= n; i ++) {
        fa[i] = fr();
        e[fa[i]].push_back(i);
        d[fa[i]] ++;
        if (fa[i] != i - 1) flag = true;
    }
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    if (!flag) {
        int sum = ans;
        for (int i = 1; i <= n; i ++) {
            sum += w[i];
            if (sum < 0) break;
            ans = max(ans,sum);
        }
        fw(ans);
        return 0;
    }
    dfs(1,0,0);
    int t = ans;
    if (ans < -w[1]) {
        fw(ans);
        return 0;
    } 
    ans += w[1];
    while (q.size()) {
        auto t = q.top();
        q.pop();
        
        if (t.se < 0) continue;
        if (t.fi <= ans) ans += t.se;
    }
    ans = max(ans,t);
    fw(ans);
    return 0;
}

Day 13

4362 贪吃的九头龙

 这个要存一下 dp 数组,不然会用更新过的数组去更新现在的数组

 然后小小的分类讨论一下

点击查看代码
int n,m,k;
vector<node> e[N];
int dp[N][N][2];
int f[N][2];
int d[N];

void dfs(int u,int from) {
    dp[u][0][0] = dp[u][1][1] = 0;
    for (auto it : e[u]) {
        int v = it.v,val = it.w;
        if (v == from) continue;
        dfs(v,u);
        memcpy(f,dp[u],sizeof f);
        memset(dp[u],0x3f,sizeof dp[u]);
        for (int i = 0; i <= k; i ++) {
            for (int j = 0; j <= i; j ++) {
                int t = min(dp[v][j][0] + val,dp[v][j][1]);
                dp[u][i][0] = min(dp[u][i][0],f[i - j][0] + t);
                t = min(dp[v][j][1] + val,dp[v][j][0]);
                dp[u][i][1] = min(dp[u][i][1],f[i - j][1] + t);
            }
        }
    }
}

void dfs1(int u,int from) {
    dp[u][0][0] = dp[u][1][1] = 0;
    for (auto it : e[u]) {
        int v = it.v,val = it.w;
        if (v == from) continue;
        dfs1(v,u);
        memcpy(f,dp[u],sizeof f);
        memset(dp[u],0x3f,sizeof dp[u]);
        for (int i = 0; i <= k; i ++) {
            for (int j = 0; j <= i; j ++) {
                int t = min(dp[v][j][0],dp[v][j][1]);
                dp[u][i][0] = min(dp[u][i][0],f[i - j][0] + t);
                t = min(dp[v][j][1] + val,dp[v][j][0]);
                dp[u][i][1] = min(dp[u][i][1],f[i - j][1] + t);
            }
        }
    }
}

int main(){
    n = fr(),m = fr(),k = fr();
    if (m - 1 + k > n) {
        wj;
        return 0;
    }
    int a,b,w;
    for (int i = 1; i < n; i ++) {
        a = fr(),b = fr(),w = fr();
        e[a].push_back({b,w});
        e[b].push_back({a,w});
    }
    memset(dp,0x3f,sizeof dp);
    if (m == 2) {
        dfs(1,0);
    }
    else {
        dfs1(1,0);
    }
    fw(dp[1][k][1]);
    return 0;
}

3609 Hoof, Paper, Scissor G

 也算是一遍过吧(),一开始有一次 k 等于 0 的时候没有走到 ans 那里

点击查看代码
int n,k;
int w[N];
int dp[N][25][4];

int main(){
    n = fr(),k = fr();
    string s;
    for (int i = 1; i <= n; i ++) {
        cin >> s;
        if (s == "P") w[i] = 1;
        else if (s == "H") w[i] = 2;
        else w[i] = 3;
    }
    memset(dp,-0x3f,sizeof dp);
    dp[0][0][1] = dp[0][0][2] = dp[0][0][3] = 0;
    int ans = 1;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= k; j ++) {
            dp[i][j][1] = dp[i - 1][j][1] + (w[i] == 3);
            dp[i][j][2] = dp[i - 1][j][2] + (w[i] == 1);
            dp[i][j][3] = dp[i - 1][j][3] + (w[i] == 2);
            if (!j) continue;
            int t = max(dp[i - 1][j - 1][2],dp[i - 1][j - 1][3]);
            dp[i][j][1] = max(dp[i][j][1],t + (w[i] == 3));
            t = max(dp[i - 1][j - 1][1],dp[i - 1][j - 1][3]);
            dp[i][j][2] = max(dp[i][j][2],t + (w[i] == 1));
            t = max(dp[i - 1][j - 1][2],dp[i - 1][j - 1][1]);
            dp[i][j][3] = max(dp[i][j][3],t + (w[i] == 2));
            ans = max(dp[i][j][1],max(dp[i][j][2],dp[i][j][3]));
        }
        for (int j = 0; j <= k; j ++)
            ans = max(dp[i][j][1],max(dp[i][j][2],dp[i][j][3]));
    }
    fw(ans);
    return 0;
}

Day 14

6103 直接自然溢出啥事没有

 这个就是有一点细节要注意,就是说这个 值 和这个 函数 是会有重复的,所以说就要在转移的时候注释掉一点东西,然后不要注释错了!!这个题也不能从前往后转移,虽然我不知道为什么,但是不重要。

点击查看代码
int n;
lwl cxpd[N],yj[N],yjk[N],hs[N],zhi[N];

// ; , {;} => 程序片段
// ; , {;} , {} => 语句
// {} , {}{} , {;} , {{;}} => 语句块
// []{;} , [](){;} , ([]{;}) , ([](){;}) => 函数
// []{;} , [](){;} , []{;}() , [](){;}() => 值
// ([]{;}) , ([](){;}) , ([]{;}()) , ([](){;}()) => 值
// []{;}; , [](){;}; , []{;}(); , [](){;}(); => 语句

int main(){
    n = fr();
    cxpd[0] = 1;
    yj[1] = 1;
    cxpd[1] = 1;
    for (int i = 2; i <= n; i ++) {
        yjk[i] += cxpd[i - 2];
        hs[i] += yjk[i - 2];
        hs[i] += hs[i - 2];
        yj[i] += yjk[i];
        if (i >= 4)
            hs[i] += yjk[i - 4];
        zhi[i] += hs[i];
        zhi[i] += zhi[i - 2];
        yj[i] += zhi[i - 1];
        for (int j = 1; j <= i; j ++) {
            cxpd[i] += cxpd[i - j] * yj[j];
        }
    }
    fw(cxpd[n]);
    return 0;
}

Day 15

5607 网格图

 也算是一遍过吧(?)。真是无语,一开始想着求稳把前面小的点用最小生成树暴力建边过,结果小的点全部都错了写的正解是对的。等会去看看是不是生成树哪里写错了。

点击查看代码
struct node{
    lwl w,id;
    bool flag;
    // true => 一列的点
    // false => 一行的点
};

int n,m;
node a[N * 2];
pii b[N];
int h[N];
lwl hang,lie;

int get(int x,int y) {
    return (x - 1) * m + y;
}

bool cmp(node a,node b) {
    return a.w < b.w;
}

int main(){
    n = fr(),m = fr();
    for (int i = 1; i <= n; i ++) {
        a[i] = {fr(),i,1};
    }
    for (int i = 1; i <= m; i ++) {
        b[i] = {fr(),i};
        a[n + i] = {b[i].fi,i,0};
    }
    sort(a + 1,a + 1 + n + m,cmp);
    lwl ans = 0;
    lwl cnt = 0;
    for (int i = 1; i <= n + m; i ++) {
        int t = 0;
        if (a[i].flag) {
            if (lie) t = 1; 
            cnt += m - 1 - t * max(hang - 1,0);
            ans += (lwl)(m - 1 - t * max(hang - 1,0)) * a[i].w;
            lie ++;
        } else {
            if (hang) t = 1;
            cnt += n - 1 - t * max(lie - 1,0);
            ans += (lwl)(n - 1 - t * max(lie - 1,0)) * a[i].w;
            hang ++;
        }
        if (cnt == (lwl)n * m - 1) break;
    }
    fw(ans),ch;
    return 0;
}

3891 采集资源

 晚上断网写的时候先写的网格图,然后感觉好困,就不想动脑子,就只写了一个 bfs 暴力,一开始还没有特判 m<T 的时候,所以只有四十分。然后补题的时候老师讲了一下,代码看了还是比较好懂的,感觉是一道不是那么难的 dp ,但是做的时候确实脑子短路了,没有想到。

点击查看代码
int n,m,T;
int dp[N][N]; // 时间,效率
pii w[N];

int main(){
    n = fr(),m = fr(),T = fr();
    int a,b;
    int idx = 0;
    if (m > T) {
        wj;
        return 0;
    }
    for (int i = 1; i <= n; i ++) {
        a = fr(),b = fr();
        if (a > T) continue;
        w[++ idx] = {a,b};
    }
    n = idx;
    memset(dp,-0x3f,sizeof dp);
    dp[0][0] = m;
    for (int i = 0; ; i ++) {
        for (int j = 0; j <= T; j ++) {
            for (int k = 1; k <= n; k ++) {
                a = w[k].fi,b = w[k].se;
                if (a <= dp[i][j]) {
                    if (j + b < T) {
                        dp[i][j + b] = max(dp[i][j + b],dp[i][j] - a);
                    } else {
                        fw(i + 1);
                        return 0;
                    }
                }
            }
            if (dp[i][j] + j >= T) {
                fw(i + 1);
                return 0;
            } else dp[i + 1][j] = max(dp[i + 1][j],dp[i][j] + j);
        }
    }
    return 0;
}

Day 16

4805 合并饭团

 是一道区间 dp 和双指针,一开始写的时候只写了 O(n4) ,后来看了题解写了双指针。
感觉区间 dp 还是不太熟

点击查看代码
int n;
int w[N];
int dp[N][N];

int main(){
    n = fr();
    int maxn = -inf;
    bool flag = false;
    int sum = 0;
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        sum += w[i];
        maxn = max(w[i],maxn);
        dp[i][i] = w[i];
        if (i > 1 && w[i] == w[i - 1]) flag = true;
        if (i > 2 && w[i] == w[i - 2]) flag = true;
    }
    if (!flag) {
        fw(maxn);
        return 0;
    }
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; l < n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            for (int i = l; i < r; i ++) {
                if (dp[l][i] && dp[l][i] == dp[i + 1][r]) {
                    dp[l][r] = dp[l][i] + dp[i + 1][r];
                    break;
                }
            }
            if (dp[l][r]) continue;
            for (int i = l,j = r; i < j - 1; ) {
                if (!dp[l][i]) i ++;
                else if (!dp[j][r]) j --;
                else if (dp[l][i] == dp[j][r]) {
                    if (dp[i + 1][j - 1]) {
                        dp[l][r] = dp[l][i] + dp[j][r] + dp[i + 1][j - 1];
                        break;
                    }
                    else i ++,j --;
                }
                else if (dp[l][i] < dp[j][r]) i ++;
                else j --;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= n; j ++) {
            ans = max(ans,dp[i][j]);
        }
    }
    fw(ans);
    return 0;
}

Day 17

7145 合法序列

 写了个暴力,感觉是先预处理出前面的2k个数,然后再往后面 dp ,但是没有想到dp方程,所以就推了。然后搞出来一个 k=2k=1 的情况,然后就这两个写了一个递推式,后面的就是递推

点击查看代码
int n,k;
lwl w[N];
lwl h[N];
lwl dp[20][N];

lwl ksm(lwl a,lwl k) {
	lwl ans = 1;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

int main(){
	n = fr(),k = fr();
	if (k == 1) {
		lwl ans = ksm(2,n - 2);
		fw(ans);
		return 0;
	}
	if (k == 2) {
		if (n == 4) {
			fw(2);
			return 0;
		}
		if (n == 5) {
			fw(4);
			return 0;
		}
		h[1] = 1,h[2] = 1;
		w[0] = 4;
		n -= 5;
		for (int i = 1; i <= n; i ++) {
			if (i > 2) h[i] = (h[i - 1] + h[i - 2]) % mod;
			w[i] = (w[i - 1] * 2 - h[i]) % mod;
		}
		fw((w[n] % mod + mod) % mod);
		return 0;
 	}
 	for (int i = 0; i < (1 << k); i ++) {
	 	for (int j = 0; j < k; j ++) {
		 	h[i] |= ((i >> j) & 1) * (1 << (k - j - 1));
		 }
	 }
	lwl ans = 0;
	int W = (1 << (1 << k)) - 1;
	int www = (1 << k) - 1;
	bool flag;
	for (int i = 0; i <= W; i ++) {
		flag = true;
		for (int j = 0; j <= www - k + 1; j ++) {
			int t = (i >> j) & www;
			t = h[t];
			if (! ((i >> t) & 1)) {
				flag = false;
				break;
			}
		}
		if (!flag) continue;
		memset(dp,0,sizeof dp);
		dp[i >> (1 << k) - k][(1 << k) - 1] = 1;
		for (int j = 1 << k; j < n; j ++) {
			for (int l = 0; l < (1 << k); l ++) {
				if ((i & (1 << h[l])) == 0)
					continue;
				int t = ((l | (1 << k - 1)) ^ (1 << k - 1)) << 1;
				dp[l][j] = (dp[t | 1][j - 1] + dp[t][j - 1]) % mod;
			}
		}
		for (int j = 0; j <= www; j ++)
			ans = (ans + dp[j][n - 1]) % mod;
	}
	fw(((ans % mod) + mod) % mod);
	return 0;
}

5583 Ethan and Sets

 区间 dp ,感觉和前面的合并饭团有一点点像,就是双指针就可以了。

点击查看代码
struct node{
    int ml,hate;
    bitset<M> t;
};

int n,m,d;
node w[N];
bool flag[M];
bool st[M];
pii dp[N][N];
lwl ml[N],hate[N];

int main(){
    n = fr(),m = fr(),d = fr();
    int a,b;
    for (int i = 1; i <= d; i ++) {
        a = fr();
        flag[a] = true;
    }
    for (int i = 1; i <= n; i ++) {
        w[i].ml = fr();
        b = fr();
        while (b --) {
            a = fr();
            if (flag[a]) w[i].t[a] = 1;
            else w[i].hate ++;
        }
        ml[i] = ml[i - 1] + w[i].ml;
        hate[i] = hate[i - 1] + w[i].hate;
    }
    memset(dp,0x3f,sizeof dp);
    bitset<M> truu;
    for (int i = 0; i <= m; i ++) {
        if (flag[i]) {
            truu[i] = 1;
        }
    }
    int l,r;
    for (l = 1; l <= n; l ++) {
        bitset<M> vis(0);
        for (r = l; r <= n; r ++) {
            vis |= w[r].t;
            if (vis == truu) break;
        }
        if (vis == truu) {
            for (; r <= n; r ++) {
                dp[l][r] = {hate[r] - hate[l - 1],ml[r] - ml[l - 1]};
            }
        }
    }
    l = 0,r = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = i; j <= n; j ++) {
            if (dp[i][j].fi == linf) continue;
            if (dp[l][r].fi == dp[i][j].fi && dp[l][r].se < dp[i][j].se) {
                l = i,r = j;
            } else if (dp[l][r].fi > dp[i][j].fi) {
                l = i,r = j;
            }
        }
    }
    if (l == 0 && r == 0) wj;
    else {
        fw(l),kg;
        fw(r),ch;
    }
    return 0;
}

Day 18

3694 邦邦的大合唱站队

 是一道状压 dp

 每一位对应的就是每一支队伍,然后如果这一位是 1 的话,就代表着这个状态下这个队伍已经被安排了,而且是安排在前面的位置。然后转移的话就看哪一支队伍是后加在最后面的,然后再从前面的状态转移

dp[i]=min(dp[ixor(1<<j)]+get(lencnt[j]+1,len,j))

点击查看代码
int n,m;
int w[N][25];
int siz[25],cnt[25];
int dp[M];
lwl ans = inf;

int get(int l,int r,int type) {
    l ++;
    return cnt[type] - w[r][type] + w[l - 1][type];
}

int main(){
    n = fr(),m = fr();
    int a;
    for (int i = 1; i <= n; i ++) {
        a = fr();
        w[i][a] = 1;
        cnt[a] ++;
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            w[i][j] += w[i - 1][j];
        }
    }
    int W = (1 << m) - 1;
    for (int i = 1; i <= W; i ++) {
        int len = 0;
        for (int j = 0; j < m; j ++) {
            if ((i >> j) & 1)
                len += cnt[j + 1];
        }
        
        dp[i] = len;
        for (int j = 1; j <= m; j ++) {
            if ((i >> (j - 1)) & 1) {
                int t = i ^ (1 << (j - 1));
                dp[i] = min(dp[i],dp[t] + get(len - cnt[j],len,j));
            }
        }
    }
    fw(dp[W]);
    return 0;
}

3847 调整队形

 这是一道区间 dp

 从题目中可以知道,他可以随便变化数列,所以就可以知道转移方程式(?),稍微感性理解一下。()

 如果当前左区间和右区间的端点相同的话,可以直接转移

dp[l][r]=dp[l+1][r1]

 如果不同的话,就可以从 dp[l + 1][r - 1] (让人变化衣服颜色)、dp[l + 1][r]&dp[l][r - 1](踢掉、增加,反正是可以凑到的)

dp[l][r]=min(dp[l+1][r1],min(dp[l+1][r],dp[l][r1]))+1

 然后就可以做了

点击查看代码
int n;
int w[N];
int dp[N][N];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; l <= n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            if (w[l] == w[r]) dp[l][r] = dp[l + 1][r - 1];
            else dp[l][r] = min(dp[l + 1][r - 1],min(dp[l + 1][r],dp[l][r - 1])) + 1;
        }
    }
    fw(dp[1][n]);
    return 0;
}

6492 STEP

 线段树,比较板子, pushup 的地方有点点麻烦,但还好。做的时候写的暴力,但是看到区间其实想到了线段树,但实在是不记得线段树怎么写了,所以只写了个暴力。

 我愿称之为线段树复建第一步(应该暂时也是最后一步()。

点击查看代码
struct node{
    int l,r; // 左右端点
    int lenl,lenr,len; // 左边连续最长,右边连续最长,最长
    int siz; // 总长度
}w[N * 4];

int n,Q;

void push_up(int idx) {
    w[idx].siz = w[il].siz + w[ir].siz;
    w[idx].l = w[il].l,w[idx].r = w[ir].r;
    
    w[idx].len = max(w[ir].len,w[il].len);
    if (w[il].r != w[ir].l)
        w[idx].len = max(w[idx].len,w[ir].lenl + w[il].lenr);
    
    w[idx].lenr = w[ir].lenr,w[idx].lenl = w[il].lenl;
    if (w[ir].lenr == w[ir].siz && w[il].r != w[ir].l) {
        w[idx].lenr = w[ir].lenr + w[il].lenr;
    }
    if (w[il].lenl == w[il].siz && w[il].r != w[ir].l) {
        w[idx].lenl = w[il].lenl + w[ir].lenl;
    }
}

void build(int l,int r,int idx) {
    if (l == r) {
        w[idx] = {1,1,1,1,1,1};
        return ;
    }
    int mid = (l + r) >> 1;
    build(l,mid,il);
    build(mid + 1,r,ir);
    push_up(idx); 
}

void modify(int L,int R,int l,int r,int idx) {
    if (L >= l && R <= r) {
        w[idx].l = 1 - w[idx].l;
        w[idx].r = w[idx].l;
        return ;
    }
    int mid = (L + R) >> 1;
    if (mid >= l) modify(L,mid,l,r,il);
    if (mid < r) modify(mid + 1,R,l,r,ir);
    push_up(idx);
}

int main(){
    n = fr(),Q = fr();
    build(1,n,1);
    int x;
    while (Q --) {
        x = fr();
        modify(1,n,x,x,1);
        fw(w[1].len);
        ch;
    }
    return 0;
}

6154 游走

 期望 dp ,差不多一遍过捏,但是一开始有几个地方没有取模,所以寄了一次

点击查看代码
int n,m;
vector<int> e[N];
int rd[N],cd[N];
lwl w[N];
lwl dis[N];

lwl ksm(lwl a,lwl k) {
    lwl ans = 1;
    while (k) {
        if (k & 1) ans = ans * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return ans;
}

lwl ny(lwl a) {
    return ksm(a,mod - 2);
}

int main(){
    n = fr(),m = fr();
    int a,b;
    while (m --) {
        a = fr(),b = fr();
        e[b].push_back(a);
        cd[b] ++,rd[a] ++;
    }
    queue<int> q;
    for (int i = 1; i <= n; i ++) {
        if (rd[i] == 0) q.push(i);
        w[i] = 1;
    }
    lwl ans = 0;
    while (q.size()) {
        auto u = q.front();
        q.pop();
        
        for (auto v : e[u]) {
            rd[v] --;
            dis[v] += w[u];
            dis[v] += dis[u];
            dis[v] %= mod;
            w[v] += w[u];
            w[v] %= mod;
            if (rd[v] == 0) q.push(v);
        }
    }
    lwl t = 0;
    for (int i = 1; i <= n; i ++) {
        t = (t + dis[i]) % mod;
        ans = (w[i] + ans) % mod;
    }
    fw(t * ny(ans) % mod);
    return 0;
}

4138 挂饰

 思路差不多是对的,就是按照那两个维度就那样,然后不要从后往前转移,以后都不要了!!!

 然后那个 dp 的第二个维度转移的时候有可能会出现负数,所以取一下 max

点击查看代码
int n;
pii w[N];
int dp[N][N];
// 第几个,空余几个挂钩

bool cmp(pii a,pii b) {
    if (a.se != b.se) return a.fi > b.fi;
    return a.se > b.se;
}

int main(){
    n = fr();
    int a,b;
    int maxn = 0;
    for (int i = 1; i <= n; i ++) {
        a = fr(),b = fr();
        w[i] = {a,b};
        maxn = max(maxn,b);
    }
    sort(w + 1,w + 1 + n,cmp);
    memset(dp,-0x3f,sizeof dp);
    for (int i = 0; i <= n; i ++) {
        dp[0][i] = -inf;
        dp[i][n + 1] = -inf;
    }
    dp[0][1] = 0;
    lwl ans = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            dp[i][j] = max(dp[i - 1][j],dp[i - 1][max(j - w[i].fi,0) + 1] + w[i].se);
        }
    }
    for (int i = 0; i <= n; i ++) {
        ans = max(ans,dp[n][i]);
    }
    fw(ans);
    return 0;
}

4095 Eden的新背包问题

 在做这个题的时候一开始写的是 n3暴力,但是后来他们在讨论的时候听了一嘴(雾),就改成正解了。

 暴力就不多说了,就是每一个去掉的都枚举一遍。然后正解的话是先把多重背包转化成01背包,然后再从前往后跑一次背包,从后往前跑一次背包。在后面每一次询问的时候再遍历一下是多少放在前面用,多少放在后面用加起来再取最大值就可以了。然后还有没有用完的情况,所以加了一小段。

点击查看代码
int n,Q;
lwl dp[N][N];
lwl f[N][N];
lwl ans[N][N];
vector<pii> w[N];

int main(){
    n = fr();
    int a,b,c;
    for (int i = 1; i <= n; i ++) {
        a = fr(),b = fr(),c = fr();
        for (int k = 1; k <= c; k ++) {
            c -= k;
            w[i].push_back({k * a,k * b});
        }
        if (c) w[i].push_back({c * a,c * b});
    }
    memset(dp,-0x3f,sizeof dp);
    memset(f,-0x3f,sizeof f);
    for (int i = 0; i <= n + 1; i ++) {
        dp[i][0] = 0;
        f[i][0] = 0;
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= 1000; j ++)
            dp[i][j] = dp[i - 1][j];
        for (auto it : w[i]) {
            int ne = it.fi,val = it.se;
            for (int j = 1000; j >= ne; j --) {
                dp[i][j] = max(dp[i][j],dp[i][j - ne] + val);
            }
        }
    }
    for (int i = n; i >= 1; i --) {
        for (int j = 0; j <= 1000; j ++)
            f[i][j] = f[i + 1][j];
        for (auto it : w[i]) {
            int ne = it.fi,val = it.se;
            for (int j = 1000; j >= ne; j --) {
                f[i][j] = max(f[i][j],f[i][j - ne] + val);
            }
        }
    }
    int max1 = 0,max2 = 0;
    for (int i = 1; i <= n; i ++) {
        max1 = 0,max2 = 0;
        for (int j = 0; j <= 1000; j ++) {
            max1 = max(dp[i][j],max1);
            dp[i][j] = max1;
            max2 = max(f[i][j],max2);
            f[i][j] = max2;
        }
    }
    int d,e;
    Q = fr();
    while (Q --) {
        lwl ans = 0;
        d = fr(),e = fr();
        d ++;
        for (int i = 0; i <= e; i ++) {
            ans = max(ans,dp[d - 1][i] + f[d + 1][e - i]);
        }
        fw(ans);
        ch;
    }
    return 0;
}

Day 19

1301 魔鬼之城

 是一道 bfs 题,但是一开始写的时候以为他那个方向的正方向和负方向算作同一个方向,所以按照这个写了,然后就寄了。

 然后发现自己好像写建边的时候容易把 j 写成 i ,以后要多多注意一下,好难找。

点击查看代码
int n,m;
bool flag[N * N][8];
int w[N][N];
int dx[8] = {1,1,-1,-1,1,-1,0,0};
int dy[8] = {1,-1,1,-1,0,0,1,-1};
vector<pii> e[N * N];
int dis[N * N][9];

int get(int x,int y) {
    return (x - 1) * m + y;
}

pii zb(int w) {
    int t = w % m;
    if (!t) {
        t = m;
        w -= m;
    }
    return {(w / m) + 1,t};
}

int bfs() {
    queue<pii> q;
    q.push({get(1,1),8});
    for (int i = 0; i < 8; i ++) {
        flag[get(1,1)][i] = true;
        dis[get(1,1)][i] = 0;
    }
    dis[get(1,1)][8] = 0;
    
    while (q.size()) {
        auto t = q.front();
        q.pop();
        
        int type = t.se,u = t.fi;
        int x = zb(u).fi,y = zb(u).se;
        
        for (auto it : e[u]) {
            if (it.se == type) continue;
            int v = it.fi;
            if (flag[v][it.se]) continue;
            flag[v][it.se] = true;
            if (v == get(n,m)) 
                return dis[u][type] + 1;
            dis[v][it.se] = dis[u][type] + 1;
            q.push({v,it.se});
        }
    }
    
    return inf;
}

int main(){
    m = fr(),n = fr();
    int x,y;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            w[i][j] = fr();
            for (int k = 0; k < 8; k ++) {
                x = i + dx[k] * w[i][j],y = j + dy[k] * w[i][j];
                if (x > n || y > m || x <= 0 || y <= 0)
                    continue;
                e[get(i,j)].push_back({get(x,y),k});
            }
        }
    }
    int ans = bfs();
    if (ans == inf) wj;
    else fw(ans);
    return 0;
}

4303 基因匹配

 一开始做的时候觉得是最长公共子序列加一些啥优化,也尝试了一些玄学优化,但是很明显失败了,而且比之前那个朴素的还要慢一些,反 向 优 化

 正解:树状数组和 dp 。因为从朴素的 dp 方程可以发现,重要的就是那几个相同的节点,在转移的时候也是取了最大值,座椅就用树状数组维护最大值,然后用 vector 存一下每一个相同的数字,在更新的时候就先把这个位置前面的最大值记录一下,接着再更新。因为这个更新方式有点乱,所以最后再在所有 dp 数组里面取一个 max

点击查看代码
int n,m;
int w[N],h[N];
int dp[N],tr[N];
vector<int> y[N];

void update(int pos,int x) {
    while (pos <= m) {
        tr[pos] = max(tr[pos],x);
        pos += (pos & -pos);
    }
}

int query(int pos) {
    int ans = 0;
    while (pos) {
        ans = max(ans,tr[pos]);
        pos -= (pos & -pos);
    }
    return ans;
}

int main(){
    n = fr();
    m = 5 * n;
    for (int i = 1; i <= 5 * n; i ++) {
        w[i] = fr();
    }
    for (int j = 1; j <= 5 * n; j ++) {
        h[j] = fr();
        y[h[j]].push_back(j);
    }
    for (int i = 1; i <= m; i ++) {
        for (int j = y[w[i]].size() - 1; j >= 0; j --) {
            int pos = y[w[i]][j];
            dp[pos] = query(pos - 1) + 1;
            update(pos,dp[pos]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= m; i ++) {
        ans = max(ans,dp[i]);
    }
    fw(ans);
    return 0;
}

5664 Emiya家今天的饭

 断网做的时候完全搞不捣吧,然后 wanqitzh 跟我们说了一下前面部分分的思路,按照这个思路写了一下试试,结果寄了,思路: 因为这个题有很多部分分的点,所以就写 m=2m=3 的点。当 m=2 的时候,每一种食材的数量都不超过一半,这也就意味着每一种食材的数量都是总食物的一半。当 m=3 的时候,就是其中一种食材不超过其它两种的和。思路具体就是这么个思路,但我确实写不出来,都快给我调吐了。

 正解是容斥 + dp ,显而易见(?),这个题目考虑反方向(不符合要求的比较容易),所以就算总的方案数减去不合法的方案数。 dp[i][j] 表示对于 col 这一列,前 i 行在 col 列中选了 j 个的方案数,那么有转移

dp[i][j]=dp[i1][j]+w[i][col]dp[i1][j1]+(sumw[i][col])dp[i1][j+1]

 然后不合法的方案数就是这一列选了超过一半的情况,也就是i=1ndp[n][n+i]

 然后考虑所有的状态,和前面的设计类似, f[i][j] 是前 i 行共选了 j 个的方案数,转移方程 :

f[i][j]=f[i1][j]+sumf[i1][j1]

 最后再一减就可以得到答案捏。

点击查看代码
int n,m;
int w[N][M],sum[N];
lwl dp[N][N * 2],f[N][N];

int main(){
    n = fr(),m = fr();
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            w[i][j] = fr();
            sum[i] = (sum[i] + w[i][j]) % mod;
        }
    }
    lwl ans = 0;
    // 计算非法方案数
    for (int col = 1; col <= m; col ++) {
        memset(dp,0,sizeof dp);
        dp[0][n] = 1;
        for (int i = 1; i <= n; i ++) {
            for (int j = n - i; j <= n + i; j ++) {
                dp[i][j] = (dp[i - 1][j] + dp[i - 1][max(0,j - 1)] * w[i][col] % mod + dp[i - 1][j + 1] * (sum[i] - w[i][col]) % mod) % mod;
            }
        }
        for (int i = 1; i <= n; i ++)
            ans = (ans + dp[n][n + i]) % mod;
    }
    // 计算合法方案数
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            int b;
            if (!j) b = 0;
            else b = 1;
            f[i][j] = (f[i - 1][j] + b * f[i - 1][max(0,j - 1)] * sum[i] % mod) % mod;
        }
    }
    lwl summ = 0;
    for (int i = 1; i <= n; i ++) {
        summ = (summ + f[n][i]) % mod;
    }
    ans = ((summ - ans) % mod + mod) % mod;
    fw(ans);
    return 0;
}

6801 花式围栏

 单调栈。考虑每一个高度分开算。一开始断网的时候调了一个小时调不出来,简直脑袋要炸了

点击查看代码
struct node{
    int h,w;
};

int n;
node a[N];

lwl y(lwl x) {
    return x * (x + 1) / 2 % mod;
}

int main(){
    n = fr();
    lwl w,h;
    for (int i = 1; i <= n; i ++) {
        a[i].h = fr();
    }
    for (int i = 1; i <= n; i ++) {
        a[i].w = fr();
    }
    stack<lwl> s;
    stack<lwl> len;
    lwl length = 0;
    lwl ans = 0;
    a[n + 1].h = 0;
    s.push(-1);
    for (int i = 1; i <= n + 1; i ++,length = 0) {
        while (a[i].h <= s.top()) {
            h = s.top();
            w = len.top();
            s.pop(),len.pop();
            length = (length + w) % mod;
            ans = ans + (y(h) - y(max(a[i].h,s.top()))) * y(length) % mod;
        }
        s.push(a[i].h);
        len.push((length + a[i].w) % mod);
    }
    fw((ans % mod + mod) % mod);
    return 0;
}

5459 回转寿司

 本来看题解应该是一道线段树或者分治的题,但是学了 rope 谁还用线段树啊(不是)。

 题目说要求这个区间的和在区间 [L,R] 之间,我们先求一个前缀和,然后这个意思就是找有多少对 (l,r) 满足 L<=sum[r]sum[l1]<=R ,那么可以转化成 sum[r]R<=sum[l1]<=sum[r]L ,然后我们就可以用一个 rope 来维护 sum 值,每次只要找针对每一个 r ,有多少个 sum[] 满足这个条件就可以了。

 然后就是 rope 里面也要开 longlong !!不要忘记了!!!

点击查看代码
lwl n,L,R;
lwl w[N];
lwl sum[N];
rope<lwl> h;

int main(){
    n = fr(),L = fr(),R = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        sum[i] = sum[i - 1] + w[i];
    }
    lwl t = 0;
    lwl ans = 0;
    lwl l,r,ll,rr;
    h.insert(0);
    for (int i = 1; i <= n; i ++) {
        t = sum[i];
        l = t - R,r = t - L;
        rr = upper_bound(h.begin(),h.end(),r) - h.begin();
        ll = lower_bound(h.begin(),h.end(),l) - h.begin();
        ans += rr - ll;
        h.insert(lower_bound(h.begin(),h.end(),t) - h.begin(),t);
    }
    fw(ans);
    ch;
    return 0;
}

4019 多边形染色

 这题断网做的时候比花式围栏还折磨,调了一个小时捏妈,人都快给我调升天了。还好代码调出来基本就是对的了,就是一开始在做的时候像个勺一样在那里枚举最后一个点的颜色然后从一号点开始遍历,枚举一号点的颜色不是更简单吗!!!!

 然后这个题一开始一直开 O2RE ,找了半天没找到是哪里, wanqitzh 帮忙盯真出来了。计算操作数量的时候那个 cnt 在我后面统计并查集缩完之后的数量时也用的这个变量,导致后面遍历操作的时候那个 cnt 用的也是这个,然后就寄了。

点击查看代码
struct node{
    int type,x,p;
}q[1005];

int n,m,c;
int col[N];
int flag[N][15];
lwl dp[N][15];
int id[N];
int h[N];

int find(int x){
    if (x != h[x]) h[x] = find(h[x]);
    return h[x];
}

int main(){
    n = fr(),m = fr(),c = fr();
    for (int i = 1; i <= n; i ++) {
        h[i] = i;
    }
    int type,x,p;
    int cnt = 0;
    while (m --) {
        type = fr(),x = fr(),p = fr();
        if (type == 3) {
            h[find(x)] = h[find(p)];
        }
        else q[++ cnt] = {type,x,p};
    }
    int idx = cnt;
    cnt = 0;
    for (int i = 1; i <= n; i ++) {
        if (h[i] != i) continue;
        id[i] = ++ cnt;
        for (int j = 1; j <= c; j ++) {
            flag[cnt][j] = 1;
        }
    }
    for (int i = 1; i <= idx; i ++) {
        type = q[i].type,x = q[i].x,p = q[i].p;
        if (type == 1) {
            for (int j = 1; j <= c; j ++) {
                if (j == p) continue;
                flag[id[find(x)]][j] = 0;
            }
        }
        else flag[id[find(x)]][p] = 0;
    }
    lwl ans = 0;
    for (int t = 1; t <= c; t ++) {
        if (! flag[id[find(1)]]) continue;
        memset(dp,0,sizeof dp);
        dp[1][t] = 1;
        for (int tt = 2; tt <= cnt; tt ++) {
            for (int j = 1; j <= c; j ++) {
                if (!flag[tt][j]) continue; 
                for (int k = 1; k <= c; k ++) {
                    if (k == j) continue;
                    dp[tt][j] = (dp[tt][j] + dp[tt - 1][k]) % mod;
                }
            }
        }
        for (int j = 1; j <= c; j ++) {
            if (j == t) continue;
            ans = (ans + dp[cnt][j]) % mod;
        }
    }
    fw(ans);
    return 0;
}

Day 20

4124 手机号码

 数位 dp ,一遍过捏

点击查看代码
lwl L,R;
int dp[N][N][N][2][2][2];
vector<int> a;

// 位数,前一、二位的数,有无四,有无八,是否限制,有无连着的三位数
lwl dfs(int w,int u1,int u2,bool si,bool ba,bool limit,bool yeah) {
    if (si && ba) return 0;
    if (!w && yeah) return 1;
    else if (!w) return 0;
    if (!limit && ~dp[w][u1][u2][yeah][si][ba])
        return dp[w][u1][u2][yeah][si][ba];
    
    lwl ans = 0;
    int up;
    if (limit) up = a[w];
    else up = 9;
    for (int i = 0; i <= up; i ++) {
        if (w == 11 && i == 0) continue;
        if ((si && i == 8) || (ba && i == 4)) continue;
        bool a = si | (i == 4),b = ba | (i == 8);
        bool c = limit & (i == up);
        if (u1 == u2 && u1 == i)
            ans += dfs(w - 1,u2,i,a,b,c,1);
        else ans += dfs(w - 1,u2,i,a,b,c,yeah);
    }
    if (limit) return ans;
    return dp[w][u1][u2][yeah][si][ba] = ans;
}

lwl wdwx(lwl x) {
    if (x == 1e10 - 1) return 0;
    a.clear();
    a.push_back(0);
    while (x) {
        a.push_back(x % 10);
        x /= 10;
    }
    return dfs(11,-2,10,0,0,1,0);
}

int main(){
    L = fr(),R = fr();
    memset(dp,-1,sizeof dp);
    lwl ans = wdwx(R) - wdwx(L - 1);
    fw(ans);
    return 0;
}

4160 生日快乐

dfs 题。一开始写的时候想的是错误贪心,而且不仅如此,还把 xyn 的输入顺序给错了,所以只有 10 分。然后把顺序改了一下,错误贪心有 70 分。

 正解就比较暴力()。但是因为写正解写完了还没有发现 nxy 写错了,所以就 TLE 了好几次,后来发现了之后就 A 了, A 了之后再拿那个错误贪心去交了个 70 分。

点击查看代码
int n;
int x,y;
double S;

// 这一块要分成 u 块,这一块的两条边
double dfs(int u,double x,double y) {
    if (u == 1) {
        if (x < y) swap(x,y);
        return x / y;
    }
    double ans = inf;
    for (int i = 1; i <= u / 2; i ++) {
        double ma1 = max(dfs(i,x * i / u,y),dfs(u - i,x * (u - i) / u,y));
        double ma2 = max(dfs(i,x,y * i / u),dfs(u - i,x,y * (u - i) / u));
        ans = min(ans,min(ma1,ma2));
    }
    return ans;
}

int main(){
    x = fr(),y = fr(),n = fr();
    S = x * y * 1.0 / n;
    double ans = dfs(n,1.0 * x,1.0 * y);
    printf("%.6lf",ans);
    return 0;
}

6767 Roses

 本来可以一遍过掉的,但是没开 long long ,所以寄。

 捆绑测试一生之敌!!!!!!!!!(捆绑测试爆零一家人整整齐齐())

点击查看代码
lwl n;
lwl a,b,c,d;

int main(){
    lwl ans;
    n = fr();
    a = fr(),b = fr(),c = fr(),d = fr();
    if (b * c > a * d) {
        swap(a,c);
        swap(b,d);
    }
    ans = n / a * b;
    if (n % a == 0) {
        fw(ans);
        return 0;
    }
    else {
        ans = linf;
        for (int i = 0; i <= a; i ++) {
            if(n <= c * i)
                ans = min(ans, d * i);
            else {
                lwl sy = n - c * i;
                if (sy % a == 0) sy = sy / a;
                else sy = sy / a + 1;
                ans = min(ans, d * i + sy * b);
            }
        }
    }
    fw(ans);
    return 0;
}

Taming the Hard G

dp ,但是不知道是啥类型的。因为 n 比较小所以可以搞 n3

 然后断网的时候写的也是错误的贪心,只有 27 分(),下次争取不写贪心()

dp[i][j] 表示在前 i 个里面出逃了 j 次最少的修改数,然后 cnt[i][j] 表示如果第 i 天出逃了,到第 j 天需要修改的数量(预处理一下)。这个转移是向后转移的(),不知道能不能向前转移,题解写的是向后转移我就写向后转移了。转移方程式:

dp[k][j]=min(dp[i)[j1]+cnt[i+1][k])(k[i+1,n])

点击查看代码
int n;
int w[N];
int dp[N][N];
// 前 i 天逃了 n 次的最小次数
int cnt[N][N];
// 第 i 天出逃到第 j 天需要修改的次数(最近一次出逃)

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    for (int i = 0; i <= n; i ++) {
        int t = 0;
        for (int j = i; j <= n; j ++) {
            if (w[j] != j - i)
                t ++;
            cnt[i][j] = t;
        }
    }
    memset(dp,0x3f,sizeof dp);
    dp[0][0] = 0;
    for (int i = 0; i <= n; i ++) {
        for (int j = 1; j <= n; j ++) {
            for (int k = i + 1; k <= n; k ++)
                dp[k][j] = min(dp[k][j],dp[i][j - 1] + cnt[i + 1][k]);
        }
    }
    for (int i = 1; i <= n; i ++) {
        fw(dp[n][i]);
        ch;
    }
    return 0;
}

6835 线性生物

 期望 dp
 本来前天做那个游走的时候感觉期望 dp 也就这样吗,然后今天就被暴击了。但是随便蒙了一个特殊性质对了十分,真是让人,大吃一惊。
 期望的线性性质:

 在这题的体现 :Ex>y=Ex>x+1+Ex+1>x+2+...+Ey1>y=i=xy1Ei>i+1

 对于这类在图上随机游走的问题,我们一般会设 Ex>x+1 表示从 x 点到 x+1 点的期望步数,那么这道题的答案就是 i=0nEx>x+1 的值。那么我们先按照期望的定义列出 Ex>x+1 的表达式( d[x] 表示这个点的出度, E(x) 表示 x 点返祖边的边集):

Ex>x+1=1d[x]1+1d[x](x,y)E(x)(Ey>x+1+1)

 将 Ex>x+1=i=yxEi>i+1 带入上式丙进行化简:

Ex>x+1=1+1d[x](x,y)E[x]i=yxEi>i+1

 此时令 fx=Ex>x+1 ,令 sum[x]=i=0xfi ,则上面的式子可以写作:

fx=1+1d[x](x,y)E[x]sum[x]sum[y1]

 发现式子两边都含有 fx ,把 fx 全部都放到式子的左边(为了方便转移),然后消去系数 1d[x] ,可以得到转移式子:

fx=(d[x]+1)+(x,y)Esum[x1]sum[y1]

 然后按照这个式子转移,因为是 x 在往后转移,所以一边算 fx 一边算 sum[x] 就可以了。最后的答案就是 sum[n]

点击查看代码
int n,m,id,maxn;
int d[N];
lwl w[N],sum[N];
vector<int> e[N];

int main(){
    id = fr();
    n = fr(),m = fr();
    if (id == 1) {
        fw(n * 2);
        return 0;
    }
    for (int i = 1; i <= n; i ++) {
        d[i] ++;
    }
    int a,b;
    while (m --) {
        a = fr(),b = fr();
        e[a].push_back(b);
        d[a] ++;
    }
    for (int x = 1; x <= n; x ++) {
        w[x] = d[x];
        for (auto y : e[x]) {
            w[x] = (w[x] + (sum[x - 1] - sum[y - 1]) % mod) % mod;
        }
        sum[x] = (sum[x - 1] + w[x]) % mod;
    }
    fw((sum[n] % mod + mod) % mod);
    return 0;
}

Day 21

7162 Sjekira

 本来可以过的,但是中间那个判断这条边走没走过的时候,一开始脑抽写错了,写的是 : id[v]<id[u] continue; 然后就寄了,后来交了之后检查的时候看出来了。但是这个样子竟然样例都过了,我要骂他的。

点击查看代码
int n;
int w[N];
int id[N];
pii sx[N];
bool flag[N];
int h[N];
vector<int> e[N];

bool cmp(int a,int b) {
    return w[a] > w[b];
}

int find(int x) {
    if (h[x] != x) h[x] = find(h[x]);
    return h[x];
}

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        id[i] = i;
    }
    sort(id + 1,id + 1 + n,cmp);
    int a,b;
    for (int i = 1; i < n; i ++) {
        a = fr(),b = fr();
        e[a].push_back(b);
        e[b].push_back(a);
    }
    int idx = 0;
    for (int i = 1; i <= n; i ++) {
        int u = id[i];
        flag[u] = true;
        for (auto v : e[u]) {
            if(flag[v]) continue;
            sx[++ idx] = {u,v};
        }
    }
    for (int i = 1; i <= n; i ++) {
        h[i] = i;
    }
    lwl ans = 0;
    for (int i = idx; i; i --) {
        int u = sx[i].fi,v = sx[i].se;
        ans += w[find(u)] + w[find(v)];
        if (w[find(u)] < w[find(v)]) h[find(u)] = find(v);
        else h[find(v)] = find(u);
    }
    fw(ans);
    return 0;
}

7149 Rectangular Pasture S

 一开始写的完全错误吧(),但是感觉好像还好的样子,也不知道哪里错了,算了,不管他

 正解:题解里面有好多用二维前缀和的,但是看不懂,所以就看的另一种方法 :先按照 first 排个序,然后再一个个跑,在每一个跑的时候考虑这个牛和他上面的牛之间有多少种子集。然后这个要看当前遍历的这个 j 是在 i 的左边还是右边,然后计算牛的数量就感性理解一下啦!

点击查看代码
int n;
pii w[N];
lwl a[N],b[N];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i].fi = fr();
        w[i].se = fr();
    }
    sort(w + 1,w + 1 + n);
    lwl ans = 1;
    for (int i = 1; i <= n; i ++) {
        ans ++;
        lwl l = 0,r = 0;
        for (int j = i - 1; j > 0; j --) {
            if (w[i].se > w[j].se) {
                ans += (r + 1) * (a[j] + 1);
                b[j] ++;
                l ++;
            } else {
                ans += (l + 1) * (b[j] + 1);
                a[j] ++;
                r ++;
            }
        }
    }
    fw(ans);
    return 0;
}

7100 团

 也算是一遍过捏,就是一开始有地方忘记开 long long 了。

 然后建边小技巧 :把每个集合搞一个点,然后都连到那个点上。

点击查看代码
int n,k;
vector<node> e[N];
pii w[N];
lwl dis[N];
bool flag[N];

void dij() {
    memset(dis,0x3f,sizeof dis);
    dis[1] = 0;
    priority_queue<pii,vector<pii>,greater<pii> > q;
    q.push({0,1});
    
    while (q.size()) {
        auto t = q.top();
        q.pop();
        
        int u = t.se;
        lwl dist = t.fi;
        if (flag[u]) continue;
        
        for (auto it : e[u]) {
            int v = it.v,w = it.w;
            if (dis[v] > dist + w) {
                dis[v] = dist + w;
                q.push({dis[v],v});
            }
        }
        
        flag[u] = true;
    }
}

int main(){
    n = fr(),k = fr();
    int m;
    int cnt = 0;
    while (k --) {
        m = fr();
        cnt ++;
        for (int i = 1; i <= m; i ++) {
            w[i].fi = fr(),w[i].se = fr();
            e[n + cnt].push_back({w[i].fi,w[i].se});
            e[w[i].fi].push_back({n + cnt,w[i].se});
        }
    }
    dij();
    for (int i = 1; i <= n; i ++) {
        if (dis[i] >= linf / 2) fw(linf);
        else fw(dis[i]);
        kg;
    }
    return 0;
}

6503 DIFERENCIJA

 一遍过捏。

点击查看代码
int n;
lwl w[N];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    stack<lwl> maxn,minn;
    stack<lwl> len1,len2;
    len1.push(0),len2.push(0);
    lwl ans = 0;
    for (int i = 1; i <= n + 1; i ++) {
        while (maxn.size() && (maxn.top() < w[i] || i == n + 1)) {
            auto t = maxn.top();
            maxn.pop();
            auto pos = len1.top();
            len1.pop();
            int len = pos - len1.top();
            ans += (lwl)len * (i - pos) * t;
        }
        while (minn.size() && (minn.top() > w[i] || i == n + 1)) {
            auto t = minn.top();
            minn.pop();
            auto pos = len2.top();
            len2.pop();
            int len = pos - len2.top();
            ans -= (lwl)len * (i - pos) * t;
        }
        minn.push(w[i]);
        len1.push(i);
        maxn.push(w[i]);
        len2.push(i);
    }
    fw(ans);
    return 0;
}

Day 22

8127 The Xana coup

 代码比较清楚捏,但是考试的时候应该写不出来()。链的可以搞一搞。

点击查看代码
int n;
int w[N];
vector<int> e[N];
int dp[N][4]; // inf => 不合法
// 0 => 权值变为 0 , 没有对节点操作 时对应的最小操作次数
// 1 => 权值变为 1 , 没有对节点操作 时对应的最小操作次数
// 2 => 权值变为 0 , 对节点进行了操作 对应的最小操作次数
// 3 => 权值变为 1 , 对节点进行了操作 对应的最小操作次数
int ou[N][2],ji[N][2]; // inf => 不合法
// 这里的操作数对应的都是子节点的操作数
// ou0 => u节点的前 i 节点全部都是 0 且有 偶数 个操作操作过
// ou1 => u节点的前 i 节点全部都是 1 且有 偶数 个操作操作过
// ji0 => u节点的前 i 节点全部都是 0 且有 奇数 个操作操作过
// ji1 => u节点的前 i 节点全部都是 1 且有 奇数 个操作操作过

void dfs(int u,int fa) {
    if (e[u].size() == 1 && fa) { // 叶子节点
        if (w[u] == 0) {
            dp[u][0] = 0;
            dp[u][1] = inf;
            dp[u][2] = inf;
            dp[u][3] = 1;
        } else {
            dp[u][0] = inf;
            dp[u][1] = 0;
            dp[u][2] = 1;
            dp[u][3] = inf;
        }
        return ;
    }
    for (auto v : e[u]) {
        if (v == fa) continue;
        dfs(v,u);
    }
    int i = 0;
    ou[0][0] = ou[0][1] = 0;
    ji[0][0] = ji[0][1] = inf;
    for (auto v : e[u]) {
        if (v == fa) continue;
        i ++;
        ou[i][0] = min(ou[i - 1][0] + dp[v][0],ji[i - 1][0] + dp[v][2]);
        ou[i][1] = min(ou[i - 1][1] + dp[v][1],ji[i - 1][1] + dp[v][3]);
        ji[i][0] = min(ji[i - 1][0] + dp[v][0],ou[i - 1][0] + dp[v][2]);
        ji[i][1] = min(ji[i - 1][1] + dp[v][1],ou[i - 1][1] + dp[v][3]);
    }
    if (w[u] == 0) {
        dp[u][0] = ou[i][0];
        dp[u][1] = ji[i][0];
        dp[u][2] = 1 + ji[i][1];
        dp[u][3] = 1 + ou[i][1];
    } else {
        dp[u][0] = ji[i][0];
        dp[u][1] = ou[i][0];
        dp[u][2] = 1 + ou[i][1];
        dp[u][3] = 1 + ji[i][1];
    }
}

int main(){
    n = fr();
    int a,b;
    bool flag = true;
    for (int i = 1; i < n; i ++) {
        a = fr(),b = fr();
        if (abs(a - b) != 1) flag = false;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
    }
    if (flag) {
        lwl ans = 0;
        for (int i = 1; i < n; i ++) {
            if (w[i]) {
                ans ++;
                w[i + 1] = 1 - w[i + 1];
                w[i + 2] = 1 - w[i + 2];
            }
        }
        if (w[n]) wj;
        else fw(ans);
        return 0;
    }
    dfs(1,0);
    lwl ans = min(dp[1][0],dp[1][2]);
    if (ans >= inf) wj;
    else fw(ans);
    return 0;
}

Day 23

7299 Dance Mooves S

 因为这个是可以不断循环的,所以说如果 i 牛在第一次循环之后到达了 j 位置,那么之后他的轨迹和 j 牛就是一样的,所以可以把他们放到一个并查集里面去。然后先把每头牛一次的轨迹存起来,最后在合并之后把所有牛群可以到达的位置都加到 set 里面去,最后在输出的时候就把每一头牛对应的牛群的 set 的大小输出出来就是可以到达的位置数量

 然后要注意的就是 i 牛一开始是在 i 地的,还要把这个加到边里面去。

点击查看代码
int n,k;​
pii w[M];​
int a[N],h[N];​
vector<int> e[N];​
set<int> ans[N];​
​
int find(int x) {​
    if (x != h[x]) h[x] = find(h[x]);​
    return h[x];​
}​
​
int main(){​
    n = fr(),k = fr();​
    for (int i = 1; i <= n; i ++) {​
        a[i] = i;​
        h[i] = i;​
        e[i].push_back(i);​
    }​
    for (int i = 1; i <= k; i ++) {​
        w[i].fi = fr();​
        w[i].se = fr();​
    }​
    for (int i = 1; i <= k; i ++) {​
        swap(a[w[i].fi],a[w[i].se]);​
        e[a[w[i].fi]].push_back(w[i].fi);​
        e[a[w[i].se]].push_back(w[i].se);​
    }​
    for (int i = 1; i <= n; i ++) {​
        int ha = find(i),hb = find(a[i]);​
        h[hb] = ha;​
    }​
    for (int i = 1; i <= n; i ++) {​
        int u = find(i);​
        for (auto v : e[i]) {​
            ans[u].insert(v);​
        }​
    }​
    for (int i = 1; i <= n; i ++) {​
        fw(ans[find(i)].size());​
        ch;​
    }​
    return 0;​
}​

9017 Lights Off G

 首先在一轮操作中,只有第一个操作需要我们选择, 所以我们把第一个操作和后面两个操作分开算。

 然后其实每一次操作的贡献只和一共进行了多少步有关。假设一共进行了 m 步,然后在第 i 步对第 j 个数字进行了操作 1 ,那么其实就意味着这个操作的贡献是对于从 j 开始的 mi+1 段连续的数字进行了异或操作(如果要循环到前面的话也是一样的)。

 考虑状压 dp 。将 dp 数组设置为 j 状态通过 i 步能不能到达 0 (最终所需要的结果)。初始化就是 dp[0][0]=true 。因为这题是多组数据,然后每一个 b 对应每一个 m 步数所给的贡献是一定的,所以我们可以预处理 dp 数组。转移方程 :

dp[i][w[j]]|=dp[i1][w[ju]

 根据题解所说:如果 j1 可以通过 j2 循环到达,那么他们的步数是一样的(虽然不知道为什么),所以把每一个状态和他循环可以到达的状态联系起来,这样在更新的时候就可以少更新一点。

 最后输入 ab 之后,再一次次模拟操作 2 和操作 3 ,看看能不能达到 0 这个状态就可以了。可以证明最多的步数不会超过 n+1 ,但是我不知道怎么证。但是我会证明最大操作数不会超过 n3 。先用 n 次操作把开关都变成 0 ,接着每一次用连续的两个操作让灯熄灭。

点击查看代码
int n,T;
int dp[LEN][M]; // j 通过 i 步能不能到达 0
int w[M];
int a,b;

int move(int x) {
    return (x >> 1) + ((x & 1) << (n - 1));
}

int main(){
    T = fr(),n = fr();
    int maxn = n * 2;
    int m = (1 << n) - 1;
    
    // 预处理
    memset(w,-1,sizeof w);
    for (int i = 0; i <= m; i ++) {
        int x = i;
        // 可以循环到达的点
        while (w[x] == -1) {
            w[x] = i;
            x = move(x);
        }
    }
    dp[0][0] = 1;
    int u = 0;
    for (int i = 1; i <= maxn; i ++) {
        u ^= 1 << ((i - 1) % n);
        for (int j = 0; j <= m; j ++) {
            dp[i][w[j]] |= dp[i - 1][w[j ^ u]];
        }
    }
    
    while (T --) {
        string s;
        cin >> s;
        a = 0,b = 0; // 多组数据
        bool flag = true;
        for (int i = 0; i < n; i ++) {
            a |= (s[i] - '0') * (1 << (n - i - 1));
            if (s[i] == '1') flag = false;
        }
        cin >> s;
        if (flag) {
            wj;
            continue;
        }
        for (int i = 0; i < n; i ++) {
            b |= (s[i] - '0') * (1 << (n - i - 1));
        }
        for (int i = 1; ; i ++) {
            a ^= b; // 模拟操作 2
            if (dp[i][w[a]]) {
                fw(i);
                ch;
                break;
            }
            b = move(b); // 模拟操作 3
        }
    }
    return 0;
}

Day 24

7300 No Time to Paint S

 因为题目范围,所以想到每一次询问都在线询问不可取,于是预处理。

 因为这题的区间中间一段是断开的,所以想到预处理前后缀。前后缀代表的就是从  1 到  i 和从  i 到  n 最小的染色数。然后预处理的时候,就用一个  flag 数组来记录当前颜色可不可以从前面延伸过来,显而易见,如果前面染  B 颜色之前有一个  A 颜色,那么这个  B 是没有办法从前面延伸过来的。所以说每一次遍历到一个 i 的时候,就把  flag 里面比  w[i] 大的  flag 标记为 0 ,也就是说没有办法从前面延伸过来。

 然后如果遍历到这个 i 的时候  flag[w[i]] 是  true ,那么就说明这个点不用再特意染色,所以直接 dp[i]=dp[i±1] 就可以了,如果要特殊染色,就  +1

 最后询问的时候把前缀和后缀拼在一起就可以了。

点击查看代码
int n,Q;
int dp[N],f[N];
int w[N];
bool flag[N];

int main(){
    n = fr(),Q = fr();
    string s;
    cin >> s;
    for (int i = 0; i < n; i ++) {
        w[i + 1] = s[i] - 'A' + 1;
    }
    memset(flag,0,sizeof flag);
    for (int i = 1; i <= n; i ++) {
        if (flag[w[i]]) dp[i] = dp[i - 1];
        else dp[i] = dp[i - 1] + 1;
        flag[w[i]] = true;
        for (int k = w[i] + 1; k <= 26; k ++) {
            flag[k] = false;
        }
    }
    memset(flag,0,sizeof flag);
    for (int i = n; i; i --) {
        if (flag[w[i]]) f[i] = f[i + 1];
        else f[i] = f[i + 1] + 1;
        flag[w[i]] = true;
        for (int k = w[i] + 1; k <= 26; k ++) {
            flag[k] = false;
        }
    }
    int l,r;
    while (Q --) {
        l = fr(),r = fr();
        fw(dp[l - 1] + f[r + 1]);
        ch;
    }
    return 0;
}

7284 Patkice II

 也算是一遍过吧,就是一开始开数组的时候没有用  NN ,所以寄了一次。

 然后这题需要  spfa 才能过。

点击查看代码
int n,m,st,en;
char c[N][N];
int dis[N * N];
bool flag[N * N];
int dx[4] = {0,0,-1,1};
int dy[4] = {1,-1,0,0};
vector<node> e[N * N];
int from[N * N];

int get(int x,int y) {
    return (x - 1) * m + y;
}

pii zb(int w) {
    int t = w % m;
    if (!t) {
        t = m;
        w -= m;
    }
    return {(w / m) + 1,t};
}

void spfa() {
    deque<int> q;
    q.push_front(st);
    memset(dis,0x3f,sizeof dis);
    dis[st] = 0;
    
    while (q.size()) {
        auto u = q.front();
        q.pop_front();
        
        flag[u] = false;
        
        for (auto it : e[u]) {
            int v = it.v;
            if (dis[v] > dis[u] + it.w) {
                dis[v] = dis[u] + it.w;
                from[v] = u;
                if (!flag[v]) {
                    flag[v] = true;
                    if (q.size() && dis[v] < dis[q.front()])
                        q.push_front(v);
                    else q.push_back(v);
                }
            }
        }
    }
}

int main(){
    n = fr(),m = fr();
    string s;
    int w,f;
    for (int i = 1; i <= n; i ++) {
        cin >> s;
        for (int j = 0; j < m; j ++) {
            c[i][j + 1] = s[j];
            w = inf;
            if (s[j] == '>') w = 0;
            else if (s[j] == '<') w = 1;
            else if (s[j] == '^') w = 2;
            else if (s[j] == 'v') w = 3;
            else if (s[j] == 'o') st = get(i,j + 1);
            else if (s[j] == '.') w = 5;
            else en = get(i,j + 1);
            if (w == inf) f = 0;
            else f = 1;
            int u = get(i,j + 1);
            for (int k = 0; k < 4; k ++) {
                if (i + dx[k] > n || i + dx[k] <= 0) continue;
                if (j + 1 + dy[k] > m || j + 1 + dy[k] <= 0) continue;
                int v = get(i + dx[k],j + 1 + dy[k]);
                if (w == k) e[u].push_back({v,f - 1});
                else e[u].push_back({v,f});
            }
        }
    }
    spfa();
    fw(dis[en]);
    ch;
    while (from[en] != st) {
        if (en == from[en] + m)
            c[zb(from[en]).fi][zb(from[en]).se] = 'v';
        else if (en == from[en] - m)
            c[zb(from[en]).fi][zb(from[en]).se] = '^';
        else if (en == from[en] + 1)
            c[zb(from[en]).fi][zb(from[en]).se] = '>';
        else if (en == from[en] - 1)
            c[zb(from[en]).fi][zb(from[en]).se] = '<';
        en = from[en];
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            cout << c[i][j];
        }
        ch;
    }
    return 0;
}

6812 Ancestor 先辈

 线段树。

 显而易见,要满足这个区间是一个先辈,只要他是一个单调不减的序列就可以,此时联想到差分,就维护序列的差分值,然后线段树里面是每一段区间的最小值,只要 [l+1,r] 这一段区间的差分最小值大于等于  0 ,那么也就说明这是一个先辈区间。

 注意 :  build 函数里面不要忘记  pushup

点击查看代码
struct node{
    lwl minn;
}w[N * 4];

int n,k;
int a[N];

void pushup(int idx) {
    w[idx].minn = min(w[ir].minn,w[il].minn);
}

void build(int l,int r,int idx) {
    if (l == r) {
        w[idx].minn = a[l];
        return ;
    }
    int mid = (l + r) >> 1;
    if (mid >= l) build(l,mid,il);
    if (mid < r) build(mid + 1,r,ir);
    pushup(idx);
}

void modify(int L,int R,int l,int r,int idx,int x) {
    if (L >= l && R <= r) {
        w[idx].minn += x;
        return ;
    }
    int mid = (L + R) >> 1;
    if (mid >= l) modify(L,mid,l,r,il,x);
    if (mid < r) modify(mid + 1,R,l,r,ir,x);
    pushup(idx);
}

lwl qurey(int L,int R,int l,int r,int idx) {
    if (L >= l && R <= r) {
        return w[idx].minn;
    }
    int mid = (L + R) >> 1;
    lwl minx = linf;
    if (mid >= l) minx = min(minx,qurey(L,mid,l,r,il));
    if (mid < r) minx = min(minx,qurey(mid + 1,R,l,r,ir));
    return minx;
}

int main(){
    n = fr(),k = fr();
    for (int i = 1; i <= n; i ++) {
        a[i] = fr();
    }
    for (int i = n; i; i --) 
            a[i] = a[i] - a[i - 1];
    build(1,n,1);
    int type,l,r,x;
    while (k --) {
        type = fr();
        if (type == 1) {
            l = fr(),r = fr(),x = fr();
            modify(1,n,l,l,1,x);
            modify(1,n,r + 1,r + 1,1,-x);
        } else {
            l = fr(),r = fr();
            if (r == n + 1) r = n;
            lwl ans = qurey(1,n,l + 1,r,1);
            if (ans < 0) wj;
            else yj;
        }
    }
    return 0;
}

7716 Covering

  dp 。因为编号小的会覆盖编号大的,所以考虑编号从小到大遍历。

 对于每一个  i ,进行分类讨论:

  1. 如果在已知图中出现了一次  i ,那么这个 i 有可能在四个方向。但是如果该方向的数字比  i 要小的话,那这个方向就是不合法的(因为如果在这个方向会覆盖掉)。四个方向全部跑一次,记录下合法的方案,再和  dp[i1][j1] 相乘就是  dp[i][j]

  2. 如果在已知图中出现了两次  i ,那么就说明  i 的位置是固定的,只能在这个位置,又因为当前纸片必须选,所以直接  dp[i][j]=dp[i1][j1] 就可以了。

  3. 如果在已知图中没有出现过  i ,那就说明  i 这个编号的纸片可选可不选。不选的话就是直接 dp[i][j]=dp[i1][j] ,选的话就需要统计当前纸片可以被完全覆盖住的地方,这里用一个前缀和来统计。因为大的点是可以覆盖小的点,所以是从后往前前缀和。

  cnt 是指到目前的  i 为止,有多少个编号是必须选的,也就是在已知图上已经出现过的编号,因为这些编号必须选,所以在后面的点的  0 cnt1 的  dp 值都是 0 ,也就是说如果选小于  cnt 个肯定是不合法的。遍历的时候就从  cnt+1 往后面遍历。

点击查看代码
int n,m,k;
int l,r;
int w[N][N];
int sum[N];
lwl dp[N][N],sl[N];
pii zb[N];
int dx[4] = {0,0,-1,1};
int dy[4] = {1,-1,0,0};

int get(int x,int y) {
    return (x - 1) * m + y;
}

int main(){
    int T = fr();
    while (T --) {
        n = fr(),m = fr(),k = fr(),l = fr(),r = fr();
        memset(w,0,sizeof w);
        memset(sum,0,sizeof sum);
        memset(sl,0,sizeof sl);
        int maxn = -inf,cnt = 0;
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= m; j ++) {
                w[i][j] = fr();
                if (!w[i][j]) continue;
                maxn = max(w[i][j],maxn);
                sl[w[i][j]] ++;
                zb[w[i][j]] = {i,j};
                if (i > 1 && w[i - 1][j])
                    sum[min(w[i - 1][j],w[i][j])] ++;
                if (j > 1 && w[i][j - 1])
                    sum[min(w[i][j - 1],w[i][j])] ++; 
            }
        }
        for (int i = maxn; i; i --)
            sum[i] = (sum[i + 1] + sum[i]) % mod;
        dp[0][0] = 1;
        for (int i = 1; i <= maxn; i ++) {
            dp[i][cnt] = dp[i - 1][cnt];
            if (sl[i] == 1) { 
                int x = zb[i].fi,y = zb[i].se;
                lwl num = 0;
                for (int k = 0; k < 4; k ++){
                    if (w[x + dx[k]][y + dy[k]] > i)
                        num ++;
                }
                for (int j = cnt + 1; j <= r; j ++) 
                    dp[i][j] = num * dp[i - 1][j - 1] % mod;
                cnt ++; 
            } else if (sl[i] == 2) {
                for (int j = cnt + 1; j <= r; j ++)
                    dp[i][j] = dp[i - 1][j - 1];
                cnt ++;
            } else {
                for (int j = cnt + 1; j <= r; j ++) {
                    dp[i][j] = (lwl)sum[i] * dp[i - 1][j - 1] % mod;
                    dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
                }
            }
        }
        lwl ans = 0;
        for (int i = max(l,cnt); i <= r; i ++) {
            ans = (ans + dp[maxn][i]) % mod;
        }
        fw(ans);
        ch;
    }
    return 0;
}

Day 25

7176 Fountain

 一遍过捏,一点倍增(?)还有一点单调栈。

点击查看代码
int n,Q;
pii w[N];
vector<node> e[N];
int dis[N],de[N];
int fa[N][20];

void dfs(int u,int father,int t) {
    if (~father) {
        de[u] = de[father] + 1;
        dis[u] = dis[father] + t;
        fa[u][0] = father;
    }
    for(int k = 1; k <= log2(de[u]); k ++)
        fa[u][k] = fa[fa[u][k - 1]][k - 1];
    for (auto it : e[u]) {
        dfs(it.v,u,it.w);
    }
}

int find(int u,int vv) {
    for (int k = log2(de[u]); k >= 0; k --) {
        if (dis[u] - dis[fa[u][k]] < vv) {
            vv -= dis[u] - dis[fa[u][k]];
            u = fa[u][k];
        }
    }
    return u;
}

int main(){
    n = fr(),Q = fr();
    w[0].fi = inf;
    for (int i = 1; i <= n; i ++) {
        w[i].fi = fr(),w[i].se = fr();
    }
    stack<int> s;
    s.push(0);
    for (int i = n; i; i --) {
        while (s.size() && w[s.top()].fi <= w[i].fi) s.pop();
        e[s.top()].push_back({i,w[i].se});
        s.push(i);
    }
    dfs(0,-1,0);
    int u,V;
    while (Q --) {
        u = fr(),V = fr();
        int ans = find(u,V);
        fw(ans);
        ch;
    }
    return 0;
}

6512 Quark and Flying Pigs

 最短路 + dp

Floyd 写错了。循环 k 要在最外层!

点击查看代码
int n,m,k;
int dis[N][N];
pii w[5005];
int dp[5005];

int main(){
    n = fr(),m = fr(),k = fr();
    int a,b,ww;
    memset(dis,0x3f,sizeof dis);
    for (int i = 1; i <= n; i ++) {
        dis[i][i] = 0;
    }
    while (m --) {
        a = fr(),b = fr(),ww = fr();
        dis[a][b] = min(dis[a][b],ww);
        dis[b][a] = dis[a][b];
    }
    for (int i = 1; i <= k; i ++) {
        w[i].fi = fr();
        w[i].se = fr();
    }
    w[0] = {0,1};
    sort(w + 1,w + 1 + k);
    for (int k = 1; k <= n; k ++) {
        for (int j = 1; j <= n; j ++) {
            if (k == j) continue;
            for (int i = 1; i <= n; i ++) {
                if (j == i || k == i) continue;
                dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);
            }
        }
    }
    dp[0] = 0;
    for (int i = 1; i <= k; i ++) {
        for (int j = 0; j < i; j ++) {
            if (w[i].fi - w[j].fi >= dis[w[i].se][w[j].se])
                dp[i] = max(dp[i],dp[j] + 1);
        }
    }
    int ans = 0;
    ans = dp[k];
    fw(ans);
    return 0;
}

6627 幸运数字

 暴力的话就是把 2000 个数字全部都遍历一遍,然后模拟一个个找。然后进一步想到其实这个数字只会在区间的端点两边。所以对于每一个区间 l , r ,除了两个端点以外,把 l1r+1 加入到要遍历的点里面去。而对于后面两个奖励条件来说,两个分别对应是区间 [l,l] 以及 [1,l1] 并上 [l+1,inf) ,然后就有三十五分了(本来理论上来说是有 40 分,不知道哪里有点问题)。

 正解:根据上面的继续往后推,把这些要对于这些有影响的点把他离散化,接着每一个区间异或一下,因为是区间,所以用差分来进行区间异或。然后异或方法就是前面的几个区间。最后再找到最大的答案就可以了。

 然后要注意的在修改第三个奖励条件的时候,修改 inf 的时候不能光修改一个很大的值,要不然会出问题。所以修改的时候就把后面的一整个区间修改一下就可以了。(好像直接修改最大值也可以,但是因为离散化之后他的点变多了所以要用 N41 来修改)

点击查看代码
struct node{
    int type;
    int a;
    int l,r;
};

int n;
node w[N];
set<int> s;
int h[N * 4];
int ans[N * 4];
// 差分

// 更改区间
void f(int l,int r,int x) {
    ans[l] ^= x;
    ans[r + 1] ^= x;
}

int main(){
    n = fr();
    int a,b,c;
    int type;
    for (int i = 1; i <= n; i ++) {
        type = fr();
        if (type == 1) {
            a = fr(),b = fr(),c = fr();
            w[i] = {type,c,a,b};
            s.insert(a - 1);
            s.insert(a);
            s.insert(b);
            s.insert(b + 1);
        } else {
            a = fr(),b = fr();
            w[i] = {type,b,a,0};
            s.insert(a);
            s.insert(a + 1);
            s.insert(a - 1);
        }
    }
    s.insert(inf);
    s.insert(-inf);
    s.insert(0);
    int cnt = 0;
    for (auto i : s) {
        h[++ cnt] = i;
    }
    for (int i = 1; i <= n; i ++) {
        w[i].l = lower_bound(h + 1,h + 1 + cnt,w[i].l) - h;
        if (w[i].type == 1) {
            w[i].r = lower_bound(h + 1,h + 1 + cnt,w[i].r) - h;
            f(w[i].l,w[i].r,w[i].a);
        } else if (w[i].type == 2) {
            f(w[i].l,w[i].l,w[i].a);
        } else {
            f(1,w[i].l - 1,w[i].a);
            f(w[i].l + 1,cnt,w[i].a);
        }
    }
    int res = ans[1],num = 1;
    for (int i = 2; i <= cnt; i ++) {
        ans[i] ^= ans[i - 1];
        if (res < ans[i]) res = ans[i],num = i;
        else if (res == ans[i] && abs(h[num]) > abs(h[i])) num = i;
        else if (res == ans[i] && abs(h[num]) == abs(h[i]) && num < i)
            num = i;
    }
    fw(res),kg;
    fw(h[num]);
    return 0;
}

4766 Outer space invaders

 区间 dp

 因为 n 很小,所以可以瞎搞 n3 的复杂度。然后这题也离散化了一下。但是这题离散化如果用 map 的话会超时,所以直接用数组就可以了,1e4 也不大。

dp[l][r] 表示的是消灭起始时间和终止时间都在 [l,r] 之间的所有外星人所需要的最短时间,在转移的时候就先找到在这个区间里面所需最大的一个距离,然后从这个外星人的左端点遍历到右端点找在什么时候对这个外星人开炮。因为如果跨越这个时间点的其它外星人是不包括在前后两个区间之内的,但是他们跨越这个时间点就说明他们可以被这一炮打掉。设 [l,r] 之间距离最大的外星人编号为 id ,那么转移方程 :

dp[l][r]=did+minlid<=k<=rid(dp[l][k1]+dp[k+1][r])

点击查看代码
struct node{
    int a,b,d;
};

int n;
node w[N];
set<int> s;
int h[10004];
int dp[N * 2][N * 2]; // 消灭这个时间内所有的外星人

bool cmp(node a,node b) {
    if (a.a == b.a) return a.b < b.b;
    return a.a < b.a;
}

int main(){
    int T = fr();
    while (T --) {
        n = fr();
        
        memset(dp,0,sizeof dp);
        s.clear();
        
        for (int i = 1; i <= n; i ++) {
            w[i].a = fr();
            w[i].b = fr();
            w[i].d = fr();
            s.insert(w[i].b);
            s.insert(w[i].a);
        }
        sort(w + 1,w + 1 + n,cmp);
        
        int cnt = 0;
        for (auto &i : s) {
            h[i] = ++ cnt;
        }
        
        for (int len = 2; len <= cnt; len ++) {
            for (int l = 1; l <= cnt; l ++) {
                int r = l + len - 1;
                if (r > cnt) break;
                int id = 0;
                for (int i = 1; i <= n; i ++) {
                    if (h[w[i].a] >= l && h[w[i].b] <= r) {
                        if (!id || w[i].d > w[id].d) id = i;
                    }
                }
                if (!id) dp[l][r] = 0;
                else {
                    dp[l][r] = inf;
                    for (int t = h[w[id].a]; t <= h[w[id].b]; t ++) {
                        // 什么时候开炮
                        dp[l][r] = min(dp[l][r],dp[l][t - 1] + dp[t + 1][r]);
                    }
                    dp[l][r] += w[id].d;
                }
            }
        }
        int ans = dp[1][cnt];
        fw(ans);
        ch;
    }
    return 0;
}

Day 26

6538 LOPOV

 一遍过捏。

点击查看代码
int n,k,maxn;
pii w[N];
multiset<int> s;

bool cmp(pii a,pii b) {
    return a.se > b.se;
}

int main(){
    n = fr(),k = fr();
    for (int i = 1; i <= n; i ++) {
        w[i].fi = fr();
        w[i].se = fr();
    }
    for (int i = 1; i <= k; i ++) {
        s.insert(fr());
    }
    sort(w + 1,w + 1 + n);
    lwl ans = 0;
    int u = 1;
    w[n + 1].fi = inf;
    priority_queue<int> q;
    for (auto m : s) {
        while (w[u].fi <= m) {
            q.push(w[u].se);
            u ++;
        }
        if (q.size()) {
            ans += q.top();
            q.pop();
        }
    }
    fw(ans);
    return 0;
}

6659 Najmniejsza

 要求连续区间的 lcm ,显而易见,相邻的两个数的 lcm 就是他们的乘积,所以可以通过 sqrt 或者二分很快的看出是不是相邻的两个数组成的。

 解决了两个数,后面就处理三个数的。通过打表可以知道三个数的乘积如果不超过 1e18 的话,那么这三个数中最小的数不会超过 1e6+1e5 ,这个复杂度可以直接暴力预处理,然后用 map 存储处理出来的数。然后在一个个遍历的时候,如果当前求出来的最小公倍数比最大值要大,那么就说明这个右边界以及后面的数都是不可以的,就可以直接 break 了。还有就是因为这里的边界是 1e18 ,如果乘起来再判断有可能会爆 lwl ,所以用除法判断。

点击查看代码
lwl a;
map<lwl,pii> w;

lwl gcd(lwl x,lwl y) {
    if (!y) return x;
    return gcd(y,x % y);
}

lwl lcm(int l,int r) {
    lwl u = 1;
    for (int i = l; i <= r; i ++) {
        u = u / gcd(u,i) * i;
    }
    return u;
}

// 预处理
void init() {
    for (lwl l = 1; l < N; l ++) {
        lwl u = l * (l + 1);
        for (lwl r = l + 2; ; r ++) {
            u /= gcd(u,r);
            if (u > linf / r) break;
            u *= r;
            if (!w[u].fi) w[u] = {l,r};
        }
    }
}

int main(){
    int T = fr();
    init();
    while (T --) {
        a = fr();
        if (a % 2) {
            wj;
            continue;
        }
        lwl t = sqrt(a);
        if (t * (t + 1) == a) {
            if (w[a].fi) {
                fw(w[a].fi),kg;
                fw(w[a].se),ch;
            }
            else {
                if (t == 2 || t == 3) fw(1);
                else fw(t);
                kg;
                fw(t + 1),ch;
            }
            continue;
        }
        if (a % 3) {
            wj;
            continue;
        }
        if (w[a].fi) {
            fw(w[a].fi),kg;
            fw(w[a].se),ch;
        } else wj;
    }
    return 0;
}

6600 字母

 感觉和 NOIP2022 的第一题 种花 有点像,但是当时比赛的时候种花是基本弄出来了(特判了特殊数据结果特殊数据都寄了),这一题没有弄出来。(CCF抄题数据还比原题弱,拉啦)。

 这一题先处理每一个点向左边,右边和下面延伸的话最多可以延伸多少个 1 ,然后再预处理每一个 T 所对应的有多少个符合条件的小 T 包含在这个 T 里面。这里是二维前缀和()。

 最后再枚举每一个中心所对应的大 T 是什么样子的,累加起来就是最后的答案。然后因为这个 T 的左右两边的横的长度是一样的,所以要在 l[i][j]r[i][j] 里面取一个 min 才是对应的大 T

 (这道题的 a 有偶数,所以说当 a 是偶数的话要 ++ ,把他转化成奇数)

点击查看代码
int n,m;
int a,b,s,x;
bool w[N][N];
int l[N][N],r[N][N],down[N][N];
int val[N][N];

int main(){
    n = fr(),m = fr();
    a = fr(),b = fr(),s = fr(),x = fr();
    a = max(3,a),b = max(2,b);
    if (a % 2 == 0) a ++;
    string ss;
    for (int i = 1; i <= n; i ++) {
        cin >> ss;
        for (int j = 0; j < m; j ++) {
            if (ss[j] == '1') {
                w[i][j + 1] = true;
                l[i][j + 1] = l[i][j] + 1;
            }
        }
    }
    for (int i = n; i; i --) {
        for (int j = m; j; j --) {
            if (w[i][j]) {
                r[i][j] = r[i][j + 1] + 1;
                down[i][j] = down[i + 1][j] + 1;
            }
        }
    }
    // 枚举 T 的形状,记录前缀和
    for (int i = a; i <= m; i += 2) {
        for (int j = b; j <= n; j ++) {
            if (i + j >= x && i * j >= s)
                val[i][j] = val[i - 2][j] + val[i][j - 1] - val[i - 2][j - 1] + 1;
        }
    }
    lwl ans = 0;
    // 枚举每一个中心然后计算
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            if (!w[i][j]) continue;
            ans += val[min(l[i][j],r[i][j]) * 2 - 1][down[i][j]];
        }
    }
    fw(ans);
    return 0;
}

4909 Ski Lift G

 一开始题目读不懂,寄了。

 代码意思挺好懂的,但是有两点要注意一下:1. 起点处是必须要建观测站的,初始化的时候初始化为 1 而不是初始化为 0 。2. 在遍历从哪一个位置转移过来的时候, j 的下限要判断一下是不是小于 0 ,不然会 RE

点击查看代码
int n,k;
int h[N];
int dp[N];

double get(int i,int j) {
    return 1.0 * (h[i] - h[j]) / (j - i);
}

int main(){
    n = fr(),k = fr();
    for (int i = 1; i <= n; i ++) {
        h[i] = fr();
    }
    memset(dp,0x3f,sizeof dp);
    dp[1] = 1;
    for (int i = 2; i <= n; i ++) {
        double maxn = -inf;
        for (int j = i - 1; j >= max(1,i - k); j --) {
            double t = get(i,j);
            if (t >= maxn) {
                maxn = t;
                dp[i] = min(dp[i],dp[j] + 1);
            }
        }
    }
    fw(dp[n]);
    return 0;
}

4377 Talent Show G

01 分数规划 + 背包。

01 规划在博客的一些杂的算法里面。二分就试一试(),我也不太记得那几个模板了。

 然后补题的时候又把 ij 写反了,气死我了。

 由题目意思可以想到用 01 分数规划,但是又不能纯纯的 01 分数规划,因为他有要求牛的重量,所以就在 01 分数规划里面套一点背包。然后所有大于所需重量的重量全部都视为 m ,这样也不用遍历或者开太大的数组。

点击查看代码
int n,m;
pii w[N];
lwl dp[1005];

bool cmp(pii a,pii b) {
    return a.se * b.fi > a.fi * b.se;
}

bool check(int x) {
    memset(dp,-0x3f,sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = m; j >= 0; j --) {
            int t = min(j + w[i].fi,m);
            dp[t]=max(dp[t],dp[j]+w[i].se - (lwl)w[i].fi * x);
        }
    }
    return dp[m] >= 0;
}

int main(){
    n = fr(),m = fr();
    int l = 0,r = -inf;
    for (int i = 1; i <= n; i ++) {
        w[i].fi = fr(),w[i].se = fr();
        w[i].se *= 1000;
        r = max(r,w[i].se / w[i].fi);
    }
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    fw(l);
    return 0;
}

Day 27

Coloring Brackets

 区间 dp ,但是这题用迭代写法比较好写(几百年没有写过 dfs 版的区间 dp 了),然后这个地方在迭代的时候需要分类讨论一下,也就是说讨论一下左右端点是不是对应的,然后分情况处理就可以了。(最后计算答案的时候不要忘记取模()

点击查看代码
int n;
char s[N];
int ma[N];
lwl dp[N][N][4][4];

void dfs(int l,int r) {
    if (r == l + 1) {
        int i = r;
        dp[i - 1][i][2][0] = 1;
        dp[i - 1][i][0][2] = 1;
        dp[i - 1][i][1][0] = 1;
        dp[i - 1][i][0][1] = 1;
        return ;
    }
    if (dp[l][r][1][0]) {
            return ;
    }
    if (ma[l] != r) {
        dfs(l,ma[l]);
        dfs(ma[l] + 1,r);
        for (int i = 0; i <= 2; i ++) 
        for (int j = 0; j <= 2; j ++) 
        for (int a = 0; a <= 2; a ++) 
        for (int b = 0; b <= 2; b ++) {
            if (j == a && j != 0) break;
            dp[l][r][i][b] += dp[l][ma[l]][i][j] * dp[ma[l] + 1][r][a][b] % mod;
            dp[l][r][i][b] %= mod;
        }
        return ;
    }
    
    int i = r - 1,j = l + 1;
    dfs(j,i);
    dp[l][r][0][1] = (dp[j][i][0][2] + dp[j][i][1][0] + dp[j][i][2][0] + dp[j][i][1][2] + dp[j][i][0][0] + dp[j][i][2][2]) % mod;
    dp[l][r][0][2] = (dp[j][i][0][1] + dp[j][i][1][0] + dp[j][i][2][0] + dp[j][i][2][1] + dp[j][i][0][0] + dp[j][i][1][1]) % mod;
    dp[l][r][1][0] = (dp[j][i][0][1] + dp[j][i][0][2] + dp[j][i][2][0] + dp[j][i][2][1] + dp[j][i][0][0] + dp[j][i][2][2]) % mod;
    dp[l][r][2][0] = (dp[j][i][0][1] + dp[j][i][1][0] + dp[j][i][0][2] + dp[j][i][1][2] + dp[j][i][0][0] + dp[j][i][1][1]) % mod;
}

int main(){
    scanf("%s",s + 1);
    n = strlen(s + 1);
    stack<int> st;
    for (int i = 1; i <= n; i ++) {
        if (s[i] == '(') st.push(i);
        else {
            ma[i] = st.top();
            ma[st.top()] = i;
            st.pop();
        }
    }
    dfs(1,n);
    lwl ans = 0;
    for (int i = 0; i <= 2; i ++) {
        for (int j = 0; j <= 2; j ++)
            ans += dp[1][n][i][j];
    }
    ans %= mod;
    fw(ans);
    return 0;
}

248 G

 区间 dp 的板子(?应该差不多吧),然后要注意的就是后面合并的时候要判断一下 dp[l][k] 是不是 0,如果是 0 的话就不能更新,要不然只有 84 分()

点击查看代码
int n;
int w[N];
int dp[N][N];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++)
        w[i] = fr();
    for (int i = 1; i <= n; i ++) dp[i][i] = w[i];
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; l <= n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            for (int k = l; k < r; k ++) {
                if (dp[l][k] == dp[k + 1][r] && dp[l][k]) {
                    dp[l][r] = max(dp[l][r],dp[l][k] + 1);
                }
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= n; j ++)
            ans = max(ans,dp[i][j]);
    }
    fw(ans);
    return 0;
}

8675 搭积木

 前缀和+区间 dp ,因为中间有减法运算,所以最后要给 ans 变成正数

点击查看代码
int n,m;
int flag[N][N];
lwl dp[N][N][N];
lwl sum[N][N];

int main(){
    n = fr(), m = fr();
    for (int i = 1; i <= n; i ++) {
        string s;
        cin >> s;
        for (int j = 1; j <= m; j ++) {
            if (s[j - 1] == '.') flag[i][j] = flag[i][j - 1];
            else flag[i][j] = flag[i][j - 1] + 1;
        }
    }
    lwl ans = 0;
    for (int l = 1; l <= m; l ++) {
        for (int r = l; r <= m; r ++) {
            if (flag[n][r] == flag[n][l - 1]) {
                dp[n][l][r] = 1;
                ans ++;
            }
        }
    }
    for (int i = n - 1; i; i --) {
        for (int l = 1; l <= m; l ++) {
            for (int r = l; r <= m; r ++) {
                sum[l][r] = dp[i + 1][l][r] + sum[l][r - 1] + sum[l - 1][r] - sum[l - 1][r - 1];
                sum[l][r] %= mod;
            }
        }
        for (int l = 1; l <= m; l ++) {
            for (int r = l; r <= m; r ++) {
                if (flag[i][r] != flag[i][l - 1]) continue;
                dp[i][l][r] = sum[l][m] - sum[l][r - 1];
                dp[i][l][r] %= mod;
                ans = (ans + dp[i][l][r]) % mod;
            }
        }
    }
    ans = (ans + 1 + mod) % mod;
    fw(ans);
    return 0;
}

イウィ

 也比较板子(,判断当前区间能不能合并,然后如果合并的话中间的一定是要消掉的,还有就是先将当前区间的 dp 值变为之前的最大值()就是里面的第一个循环

点击查看代码
int n;
char s[N];
int dp[N][N];

int main(){
    scanf("%s", (s + 1));
    n = strlen(s + 1);
    for (int len = 3; len <= n; len ++) {
        for (int l = 1; l <= n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            for (int k = l; k < r; k ++)
                dp[l][r] = max(dp[l][r],dp[l][k] + dp[k + 1][r]);
            if (s[r] == 'w' || s[l] == 'w') continue;
            for (int k = l + 1; k < r; k ++) {
                if (s[k] == 'i') continue;
                if (dp[l + 1][k - 1] * 3 >= (k - l - 1) && dp[k + 1][r - 1] * 3 >= (r - k - 1))
                    dp[l][r] = max(dp[l][r], dp[l + 1][k - 1] + dp[k + 1][r - 1] + 1);
            }
        }
    }
    int ans = 0;
    for (int l = 1; l <= n; l ++) {
        for (int r = l; r <= n; r ++)
            ans = max(ans,dp[l][r]);
    }
    fw(ans);
    ch;
    return 0;
}

Letter Pickings

 可以证明 Bob 不会赢,所以判断一下 Alice 是赢还是平局,在每一个区间分两种情况,取左端点和右端点,只要其中有一个情况是 Alice 必胜的,那么就更新一下 dp 值。

点击查看代码
int n;
char s[N];
bool dp[N][N];

int main(){
    int T = fr();
    while (T --) {
        memset(dp,0,sizeof dp);
        scanf("%s",s + 1);
        n = strlen(s + 1);
        for (int len = 2; len <= n; len ++) {
            for (int l = 1; l <= n; l ++) {
                int r = l + len - 1;
                if (r > n) break;
                if ((s[l] > s[l + 1] || dp[l + 2][r]) &&
                    (s[l] > s[r] || dp[l + 1][r - 1]))
                    dp[l][r] = true;
                if ((s[r] > s[r - 1] || dp[l][r - 2]) &&
                    (s[r] > s[l] || dp[l + 1][r - 1]))
                    dp[l][r] = true;
            }
        }
        if (dp[1][n]) ay;
        else pj;
    }
    return 0;
}

Day 28

Zuma

 一遍过捏,直接区间

点击查看代码
int n;
int w[N];
int dp[N][N];

int main(){
    n = fr();
    memset(dp,0x3f,sizeof dp);
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        dp[i][i] = 1;
        if (w[i] == w[i - 1]) dp[i - 1][i] = 1;
        else dp[i - 1][i] = 2;
    }
    for (int len = 3; len <= n; len ++) {
        for (int l = 1; l <= n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            if (w[r] == w[l]) dp[l][r] = dp[l + 1][r - 1];
            for (int k = l; k < r; k ++)
                dp[l][r] = min(dp[l][r],dp[l][k] + dp[k + 1][r]);
        }
    }
    fw(dp[1][n]);
    return 0;
}

1220 关路灯

 一遍过捏。一开始有一个地方的 1 写成 0 了,调了好久。烦。

点击查看代码
int n,st;
lwl dp[N][N][2];
// 0 在左端点
// 1 在右端点
pii w[N];
lwl sum[N];

lwl get(int l,int r) {
    if (l > r) swap(l,r);
    return sum[n] - (sum[r] - sum[l - 1]);
}

lwl dis(int a,int b) {
    return abs(w[a].fi - w[b].fi);
}

int main(){
    n = fr(), st = fr();
    for (int i = 1; i <= n; i ++) {
        w[i] = {fr(), fr()};
        sum[i] = sum[i - 1] + w[i].se;
    }
    memset(dp,0x3f,sizeof dp);
    dp[st][st][0] = dp[st][st][1] = 0;
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; l <= n; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            dp[l][r][0] = min(dp[l + 1][r][0] + dis(l + 1,l) * get(l + 1,r),dp[l + 1][r][1] + dis(r,l) * get(l + 1,r));
            dp[l][r][1] = min(dp[l][r - 1][0] + dis(l,r) * get(r - 1,l),dp[l][r - 1][1] + dis(r - 1,r) * get(r - 1,l));
            int t = 0;
        }
    }
    
    fw(min(dp[1][n][0],dp[1][n][1]));
    return 0;
}

9119 圣诞树

 一遍过捏。区间 dp 并且记录一下是从哪里转移过来的。断网做的时候一开始 dis 函数里面有一个 se 写成 fi ,死活找不出来问题在那里。怒。

点击查看代码
int n,k;
pdd w[N];
double dp[N][N][2];
int pre[N][N][2];

int id(int i) {
    if (i > n) return i - n;
    if (i <= 0) return i + n;
    return i;
}

double dis(int a,int b) {
    int i = id(a), j = id(b);
    double dx = w[i].fi - w[j].fi;
    double dy = w[i].se - w[j].se;
    return sqrt(dx * dx + dy * dy);
}

int main(){
    n = fr();
    double maxn = -dinf;
    for (int i = 1; i <= n; i ++) {
        cin >> w[i].fi;
        cin >> w[i].se;
        if (maxn < w[i].se) {
            maxn = w[i].se;
            k = i;
        }
    }
    for (int l = 0; l < n; l ++) {
        for (int r = 0; r < n; r ++)
            dp[l][r][0] = dp[l][r][1] = dinf;
    }
    dp[0][0][1] = dp[0][0][0] = 0;
    memset(pre,-1,sizeof pre);
    for (int len = 1; len < n; len ++) {
        for (int l = 0; l <= len; l ++) {
            // 向左延伸 & 向右延伸
            int r = len - l;
            if (l && dp[l][r][0] > dp[l - 1][r][0] + dis(k-l+1,k - l)) {
                dp[l][r][0] = dp[l - 1][r][0] + dis(k - l + 1,k - l);
                pre[l][r][0] = 0;
            }
            if (l && dp[l][r][0] > dp[l - 1][r][1] + dis(k - l,k + r)) {
                dp[l][r][0] = dp[l - 1][r][1] + dis(k - l,k + r);
                pre[l][r][0] = 1;
            }
            if (r && dp[l][r][1] > dp[l][r - 1][1] + dis(k+r-1,k + r)) {
                dp[l][r][1] = dp[l][r - 1][1] + dis(k + r - 1,k + r);
                pre[l][r][1] = 1;
            }
            if (r && dp[l][r][1] > dp[l][r - 1][0] + dis(k - l,k + r)) {
                dp[l][r][1] = dp[l][r - 1][0] + dis(k - l,k + r);
                pre[l][r][1] = 0;
            }
        }
    }
    double ans = dinf;
    int L,R,t;
    for (int l = 0; l < n; l ++) {
        int r = n - 1 - l;
        if (dp[l][r][0] < ans) {
            ans = dp[l][r][0];
            L = l, R = r, t = 0;
        }
        if (dp[l][r][1] < ans) {
            ans = dp[l][r][1];
            L = l, R = r, t = 1;
        }
    }
    stack<int> path;
    while (~pre[L][R][t]) {
        if (t) {
            path.push(k + R);
            t = pre[L][R][t];
            R = R - 1;
        } else {
            path.push(k - L);
            t = pre[L][R][t];
            L = L - 1;
        }
    }
    path.push(k);
    while (path.size()) {
        fw(id(path.top()));
        kg;
        path.pop();
    }
    return 0;
}

Make Pair

 在断网的时候死活想不出来该怎么去去重,然后发现原来做法都有点问题,恼。

 用这种组合数学的方法的话就不需要去重,用一下组合数就可以了。

点击查看代码
int n,m;
set<int> s[N];
lwl dp[N][N];
lwl f[N][N];
lwl C[N][N];

void init() {
	C[0][0] = 1;
	for (int i = 1; i < N; i ++) {
		C[i][0] = 1;
		for (int j = 1; j <= i; j ++) {
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
		}
	}
}

int main(){
	n = fr(), m = fr();
	for (int i = 1; i <= m; i ++) {
		int a = fr(),b = fr();
		if (a > b) swap(a,b);
		s[a].insert(b);
		if (b - a == 1) dp[a][b] = dp[b][a] = 1;
	}
	init();
	for (int len = 4; len <= n * 2; len += 2) {
		int t = len >> 1;
		for (int l = 1; l <= n * 2; l ++) {
			int r = len + l - 1;
			if (r > n * 2) break;
			if (s[l].find(r) != s[l].end())
				dp[l][r] = dp[l + 1][r - 1];
			for (int k = l + 2; k < r; k += 2) {
				if (s[k].find(r) != s[k].end()) {
					dp[l][r] += dp[l][k - 1] * dp[k + 1][r - 1] % mod * C[t][(r - k + 1) / 2] % mod;
					dp[l][r] %= mod;
				} else if (s[l].find(k) != s[l].end()) {
					dp[l][r] += dp[l + 1][k - 1] * dp[k + 1][r] % mod * C[t][(k - l + 1) / 2] % mod;
					dp[l][r] %= mod;
				}
			}
		}
	}
	fw(dp[1][2 * n]);
	ch;
	return 0;
}

Day 29

7914 括号序列

 分六种情况存储:

  1. dp[l][r][0] => 对应的是全部都是 的情况(也就是说存一下当前区间是不是 S
  2. dp[l][r][1] => 对应的是左右两边分别是左括号和右括号,并且这两个括号互相对应
  3. dp[l][r][2] => 对应的是 AS
  4. dp[l][r][3] => 对应的是 ASBdp[l][r][1] 的情况包括在内)
  5. dp[l][r][4] => 对应的是 SA
  6. dp[l][r][5] => 两侧都是 的情况( dp[l][r][0] 的情况包括在内)

 然后再按照这种存储转移就行了,然后显然 dp[1][n][3] 所对应的就是最终答案,所以输出的时候直接输出这个就可以了。

 然后代码中更新 dp[l][r][3] 的时候后面用的是 dp[k+1][r] 其实就是为了去重

点击查看代码
int n,k;
int w[N];
lwl dp[N][N][6];
// 0 ***    1 (...)    2 (...)**(...)**
// 3 (...)**(...)   4 **(...)**(...)     5 **(...)...(...)**

int main(){
    n = fr(), k = fr();
    string s;
    cin >> s;
    for (int i = 1; i <= n; i ++) {
        if (s[i - 1] == '(') w[i] = 1;
        else if (s[i - 1] == ')') w[i] = 2;
        else if (s[i - 1] == '*') w[i] = -2;
        else w[i] = -1;
    }
    for (int i = 0; i < n; i ++) dp[i + 1][i][0] = 1;
    for (int len = 1; len <= n; len ++) {
        for (int l = 1; ; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            if (len <= k && w[l] < 0)
                dp[l][r][0] = dp[l + 1][r][0];
            if (len >= 2) {
                if ((w[l] == 1 || w[l] == -1) && (w[r] == -1 || w[r] == 2))
                dp[l][r][1] = (dp[l + 1][r - 1][0] + dp[l + 1][r - 1][2] + dp[l + 1][r - 1][3] + dp[l + 1][r - 1][4]) % mod;
                for (int k = l; k < r; k ++) {
                    dp[l][r][2] = (dp[l][r][2] + dp[l][k][3] * dp[k + 1][r][0]) % mod;
                    dp[l][r][3] = (dp[l][r][3] + (dp[l][k][2] + dp[l][k][3]) * dp[k + 1][r][1]) % mod;
                    dp[l][r][4] = (dp[l][r][4] + dp[l][k][0] * dp[k + 1][r][3]) % mod;
                    dp[l][r][5] = (dp[l][r][5] + dp[l][k][4] * dp[k + 1][r][0]) % mod;
                }
            }
            dp[l][r][3] = (dp[l][r][3] + dp[l][r][1]) % mod;
            dp[l][r][5] = (dp[l][r][5] + dp[l][r][0]) % mod;
        }
    }
    fw(dp[1][n][3]);
    return 0;
}

6879 スタンプラリー 3

 在信友队的时候做过这道题,一遍过捏()

点击查看代码
int n,L;
pii w[N];
int dp[N][N][N][2];

void mn(int &a,int b) {
    if (a > b) a = b;
}

int dis(int a,int b) {
    return abs(w[a].fi - w[b].fi);
}

signed main(){
    n = fr(), L = fr();
    for (int i = 1; i <= n; i ++)
        w[i].fi = fr();
    for (int i = 1; i <= n; i ++)
        w[i].se = fr();
    memset(dp,0x3f,sizeof dp);
    dp[0][0][0][0] = dp[0][0][0][1] = 0;
    w[n + 1] = {L,-inf};
    for (int l = 0; l <= n; l ++) {
        for (int r = 0; r <= n; r ++) {
            if (l + r >= n) break;
            for (int k = 0; k <= n; k ++) {
                int u = dp[l][r][k][0];
                int fl;
                
                if (u + dis(n - l + 1,n - l) <= w[n - l].se) 
                    fl = 1;
                else fl = 0;
                mn(dp[l + 1][r][k + fl][0],u + dis(n - l + 1,n - l));
                
                if (u + L - dis(r + 1,n - l + 1) <= w[r + 1].se) 
                    fl = 1;
                else fl = 0;
                mn(dp[l][r + 1][k + fl][1],u + L - dis(r + 1,n - l + 1));
                
                u = dp[l][r][k][1];
                
                if (u + dis(r + 1,r) <= w[r + 1].se)
                    fl = 1;
                else fl = 0;
                mn(dp[l][r + 1][k + fl][1],u + dis(r + 1,r));
                
                if (u + L - dis(r,n - l) <= w[n - l].se)
                    fl = 1;
                else fl = 0;
                mn(dp[l + 1][r][k + fl][0],u + L - dis(r,n - l));
            }
        }
    }
    for (int k = n; k; k --) {
        for (int i = 0; i <= n; i ++) {
            for (int j = 0; j <= n; j ++) {
                if (dp[i][j][k][0] < linf || dp[i][j][k][1] < linf) {
                    fw(k);
                    return 0;
                }
            }
        }
    }
    fw(0);
    return 0;
}

ACwing1222 密码脱落

 区间 dp ,直接瞎搞!

 如果当前的 w[l]w[r] 一样的话,那么他就可以和两个中间的密码形成一个回文串,就不用多加了,但是如果不一样的话就要多加一个

点击查看代码
int n;
int w[N];
int dp[N][N];

int main(){
    string s;
    cin >> s;
    n = s.length();
    memset(dp,0x3f,sizeof dp);
    for (int i = 1; i <= n; i ++) {
        if (s[i - 1] == 'A') w[i] = 1;
        else if (s[i - 1] == 'B') w[i] = 2;
        else if (s[i - 1] == 'C') w[i] = 3;
        else w[i] = 4;
        dp[i][i] = 0;
        dp[i][i - 1] = 0;
    }
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; ; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            if (w[l] == w[r]) dp[l][r] = dp[l + 1][r - 1];
            else dp[l][r] = min(dp[l + 1][r],dp[l][r - 1]) + 1;
        }
    }
    fw(dp[1][n]);
    return 0;
}

3147 262144 P

 是前面那道 248G 的双倍经验,只需要用那个题的倍增然后改一下数组大小就可以了

点击查看代码
int n;
int w[N][100];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) {
        int a = fr();
        w[i][a] = i + 1;
    }
    int ans = 0;
    for (int j = 1; j <= 58; j ++) {
        for (int i = 1; i <= n; i ++) {
            if (!w[i][j])
                w[i][j] = w[w[i][j - 1]][j - 1];
            if (w[i][j]) ans = j;
        }
    }
    fw(ans);
    return 0;
}

ABC 252G Pre-Order

 区间 dp ,在 lr 内枚举虚拟根为 l 的时候,作为这两个点的中间点点 k (也就是子树的末尾)

 因为题目中说了如果同为一个节点的儿子节点先遍历编号小的,所以在转移的时候需要加一个判断:

p[l+1]<=p[k+1]

 或者说 kr 相等也是可以的。然后直接按照方案数计算的方法算就好啦!

点击查看代码
int n;
int p[N];
lwl dp[N][N];

int main(){
    n = fr();
    for (int i = 1; i <= n; i ++) p[i] = fr();
    for (int i = 1; i <= n; i ++) dp[i][i] = 1;
    for (int len = 2; len <= n; len ++) {
        for (int l = 1; ; l ++) {
            int r = l + len - 1;
            if (r > n) break;
            for (int k = l; k <= n; k ++) {
                if (p[l + 1] <= p[k + 1] || k == r)
                    dp[l][r] = (dp[l][r] + dp[l + 1][k] * dp[k][r] % mod) % mod;
            }
        }
    }
    fw(dp[1][n]);
    return 0;
}

4539 zh_tree

 一眼 n<30 ,这不瞎搞!直接 dfs 记搜搞起!(复杂度也不是很高吧吧吧)

 初始化的话把所有值搞到最大值就可以了,然后 double 不要用 memset ,一个个赋值啦。因为题目中已经给了中序遍历的结果了,所以直接枚举中序遍历中的中间点是哪一个然后往两边子树递归求就可以了。

 然后这里 dfs 的类型不要搞错了!也要用 double !!

点击查看代码
int n;
double t,c;
int w[N];
double f[N];
double dp[N][N][N];
// l r => 区间
// h => 这个区间的最浅深度

double dfs(int l,int r,int h) {
    if (l > r) return 0;
    if (dp[l][r][h] < dinf) return dp[l][r][h];
    for (int k = l; k <= r; k ++) {
        double temp = dp[k][k][h];
        // 以 k 为根
        temp += dfs(l,k - 1,h + 1);
        temp += dfs(k + 1,r,h + 1);
        dp[l][r][h] = min(dp[l][r][h],temp);
    }
    return dp[l][r][h];
}

int main(){
    n = fr();
    cin >> t >> c;
    int sum = 0;
    for (int i = 1; i <= n; i ++) {
        w[i] = fr();
        sum += w[i];
    }
    for (int i = 1; i <= n; i ++) {
        f[i] = 1.0 * w[i] / sum;
    }
    for (int i = 1; i <= n; i ++) {
        for (int k = 1; k <= n; k ++) {
            for (int j = 1; j <= n; j ++) {
                dp[i][j][k] = dinf;
            }
            dp[i][i][k] = (k * t + c) * f[i];
        }
    }
    dfs(1,n,1);
    printf("%.3lf",dp[1][n][1]);
    return 0;
}

Day 30

AGC058B Adjacent Chmax

 这个题一开始做的时候,完全不会做吧,然后就直接把这一题放弃了跑去做第三题了

 然后开网之后看题解,我草,这么短的代码,比我在一开始写的时候写的预处理都要短。

 正解也是先用单调栈处理出左边和右边第一个比这个数大的数在哪里(记录的时候记录比这个数大的数的前一个或者后一个(也就是记录一下可以延续的区间))

 然后处理之后就可以直接转移了。(一开始写单调栈的时候又把 while 写成 if 了!!!)

点击查看代码
int n;
int w[N];
int L[N],R[N];
lwl dp[N];

void init() {
	stack<int> s;
	for (int i = 1; i <= n; i ++) {
		while (s.size() && w[s.top()] < w[i]) s.pop();
		if (s.size()) L[i] = s.top() + 1;
		else L[i] = 1;
		s.push(i);
	}
	while (s.size()) s.pop();
	for (int i = n; i; i --) {
		while (s.size() && w[s.top()] < w[i]) s.pop();
		if (s.size()) R[i] = s.top() - 1;
		else R[i] = n;
		s.push(i);
	}
}

int main(){
	n = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
	}
	init();
	dp[0] = 1;
	for (int i = 1; i <= n; i ++) {
		for (int j = L[i]; j <= R[i]; j ++)
			dp[j] = (dp[j] + dp[j - 1]) % mod;
	}
	fw(dp[n]);
	return 0;
}

5336 成绩单

 区间 dp ,然后用 dfs 求解。影响区间值的有区间的左端点和右端点,以及对应区间求解的时候所用的 maxnminn ,所以用一个四维的 dp 数组来存储这个对应的答案,再用一个 cost 数组来存储每一个区间总的对应的答案。

 然后因为这个 w 稍微有一点点大,所以还需要离散化一下。

点击查看代码
int a, b;
int n, idx;
int w[N], y[N];
int h[1005];
lwl dp[N][N][N][N];
// l r minn maxn
// 取出 [l, r] , 极值为 minn , maxn 的最小代价
lwl cost[N][N]; // l,r 的最小代价

void init() {
	memset(dp,0x3f,sizeof dp);
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= w[i]; j ++) {
			for (int k = w[i]; k <= idx; k ++) {
				dp[i][i][j][k] = 0;
			}
		}
	}
	memset(cost,0x3f,sizeof cost);
	for (int i = 1; i <= n; i ++) cost[i][i] = a;
}

lwl get(int i,int j) {
	return a + b * (y[j] - y[i]) * (y[j] - y[i]);
}

void dfs(int l,int r) {
	if (cost[l][r] < linf) return ;
	for (int k = l; k < r; k ++) {
		dfs(l,k), dfs(k + 1,r);
		for (int i = 1; i <= idx; i ++) {
			for (int j = i; j <= idx; j ++) {
				lwl &u = dp[l][r][i][j];
				// 枚举极值在的三种地方
				// 在左区间
				u = min(u,dp[l][k][i][j] + cost[k + 1][r]);
				// 在右区间
				u = min(u,cost[l][k] + dp[k + 1][r][i][j]);
				// 左右都有
				u = min(u,dp[l][k][i][j] + dp[k + 1][r][i][j]);
				// 更新答案
				cost[l][r] = min(cost[l][r],u + get(i,j));
			}
		}
	}
}

int main(){
	n = fr();
	a = fr(), b = fr();
	
	// 离散化
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		y[i] = w[i];
	}
	sort(y + 1,y + 1 + n);
	unique(y + 1,y + 1 + n);
	for (int i = 1; i <= n; i ++) {
		if (!h[y[i]]) h[y[i]] = ++ idx;
	}
	for (int i = 1; i <= n; i ++) {
		w[i] = h[w[i]];
	}
	
	init();
	dfs(1,n);
	lwl ans = cost[1][n];
	fw(ans);
	return 0;
}

5052 Go

 一开始断网的时候写的代码只有 92pts ,十分生气。而且这一题不让下数据在讨论区薅的几个 hack 都被我过掉了,所以实在不知道该怎么改了,所以在提交记录里面找到一个和我错的一样的,然后用他的 92pts 和他的 100pts 进行了一下代码对比,然后就找到问题了。

 就这个代码如果起点比小精灵所在的号码最大的房子要大的话,他是没有更新起点的,所以要加一个特判。

点击查看代码
struct node{
	int a,b,t;
};

int n, k, m;
node w[N];
int dp[N][N][2005][2];
int ans = 0;

int dis(int x,int y) {
	return abs(w[x].a - w[y].a);
}

void mx(int &a, int b) {
	if (a < b) a = b;
}

int main(){
	n = fr(), k = fr(), m = fr();
	int maxn = 0;
	int st = 0;
	for (int i = 1; i <= m; i ++) {
		w[i] = {fr(), fr(), fr()};
		maxn = max(maxn,w[i].t);
		if (w[i].a > k && w[i - 1].a < k) {
			w[i + 1] = w[i];
			w[i] = {k,0,0};
			st = i;
			m ++;
			i ++;
		} else if (w[i].a == k) st = i;
	}
	if (!st) {
		m ++;
		w[m] = {k,0,0};
		st = m;
	}
	for (int len = 1; len <= m; len ++) {
		for (int l = 0; l <= len && st - l > 0; l ++) {
			int r = len - l;
			if (st + r > m) continue;
			for (int t = maxn; t >= 0; t --) {
				dp[l][r][t][0] = dp[l][r][t][1] = -inf; 
				int fl = 0;
				if (t < w[st - l].t) fl = 1;
				int fr1 = 0;
				if (t < w[st + r].t) fr1 = 1;
				int u;
				if (l)
				u = t - dis(st - l,st - l + 1);
				if (u >= 0 && l)
					mx(dp[l][r][t][0], dp[l - 1][r][u][0] + w[st - l].b * fl);
				if (r)
				u = t - dis(st + r,st + r - 1);
				if (u >= 0 && r) 
					mx(dp[l][r][t][1], dp[l][r - 1][u][1] + w[st + r].b * fr1);
				u = t - dis(st - l, st + r);
				if (u >= 0) {
					if (r)
					mx(dp[l][r][t][1], dp[l][r - 1][u][0] + w[st + r].b * fr1);
					if (l)
					mx(dp[l][r][t][0], dp[l - 1][r][u][1] + w[st - l].b * fl);
				}
				ans = max(ans,max(dp[l][r][t][0], dp[l][r][t][1]));
			}
		}
	}
	ans += w[st].b;
	fw(ans);
	return 0;
}

ABC163E Active infants

 先排个序,然后跑一遍区间 dp ,再加一点点贪心就搞完了()。贪心的证明就冒过去啦!

点击查看代码
int n;
pii w[N];
lwl dp[N][N];

int main(){
	n = fr();
	for (int i = 1; i <= n; i ++) {
		w[i].fi = fr();
		w[i].se = i;
	}
	sort(w + 1,w + 1 + n);
	for (int len = 1; len <= n; len ++) {
		for (int l = 1; ; l ++) {
			int r = l + len - 1;
			if (r > n) break;
			dp[l][r] = max(dp[l][r],dp[l + 1][r] + (lwl)w[len].fi * abs(w[len].se - l));
			dp[l][r] = max(dp[l][r],dp[l][r - 1] + (lwl)w[len].fi * abs(w[len].se - r));
		}
	}
	fw(dp[1][n]);
	return 0;
}

4302 字符串折叠

 这个题目因为要求一样的才能折叠,所以为了后面方便匹配可以先预处理一下哈希(但是好像不用预处理直接暴力匹配也可以过),然后在转移的时候分两种情况讨论。

  1. 不发生新的折叠,那么就是直接枚举断点,将左半边和右半边加在一起就是答案了。

dp[l][r]=min(dp[l][k]+dp[k+1][r])

  1. 在这个区间发生之前没有的折叠,因为要折叠的话就把整段都折叠起来,不然就和之前的状态会有重复,所以就枚举把这一段分成多少小段(这个段数和区间长度要能够整除!),判断的话就用哈希就可以了。然后前面还处理一下每一个数字的长度,这个地方就可以写成( k 是每一段的长度):

dp[l][r]=min(dp[l][l+k1]+Len[len/k])

点击查看代码
const int P = 2333;

int n;
int Len[N];
int w[N];
lwl hsh[N], p[N];
int dp[N][N];
char s[N];

void init() {
	for (int i = 1; i <= 9; i ++) Len[i] = 3;
	for (int i = 10; i <= 99; i ++) Len[i] = 4;
	Len[100] = 5;
	p[0] = 1;
	for (int i = 1; i <= n; i ++) {
		hsh[i] = hsh[i - 1] * P + s[i];
		p[i] = p[i - 1] * P;
	}
}

lwl get(int l,int r) {
	return hsh[r] - hsh[l - 1] * p[r - l + 1];
}

bool fold(int l,int r,int len) {
	lwl t = 0;
	for (int i = l, j = l + len - 1; j <= r; i += len, j += len) {
		if (!t) t = get(i,j);
		else if (get(i,j) != t) return false;
	}
	return true;
}

int main(){
	scanf("%s",(s + 1));
	n = strlen(s + 1);
	memset(dp,0x3f,sizeof dp);
	for (int i = 1; i <= n; i ++) {
		dp[i][i] = 1;
	}
	init();
	for (int len = 2; len <= n; len ++) {
		for (int l = 1; ; l ++) {
			int r = l + len - 1;
			if (r > n) break;
			// 不新折叠
			for (int k = l; k < r; k ++)
				dp[l][r] = min(dp[l][r],dp[l][k] + dp[k + 1][r]);
			// 折叠(整段折叠)
			for (int k = 1; k <= len; k ++) {
				if (len % k) continue;
				if (fold(l,r,k))
					dp[l][r] = min(dp[l][r],dp[l][l + k - 1] + Len[len / k]);
			}
		}
	}
	fw(dp[1][n]);
	return 0;
}

2470 压缩

 这一题的状态设置好妙。

dp[l][r][0] 表示的是在这个区间里面没有包含 M 的最短长度, dp[l][r][1] 表示的则是包含的情况。

 这样设置状态是因为这一题的 R 是有对应的 M 的,也就是说如果小的区间里面有了 M ,那么包含他的大区间就会受到小区间里面的 M 的影响。然后这样设置的时候,我们在转移的时候都假设每一个区间的前面都跟了一个 M

 于是在转移的时候,我们也分成两种情况来转移,第一种就是这个区间里面没有 M 的情况,那么枚举断点之后我们就不能用 dp[l][r][0]=min(dp[l][k][0]+dp[k][r+1][0]) 了,因为这个式子还包含了 k 这个位置也有一个 M 的情况,那么就和状态设置相违背了。所以我们就左半段折叠(也可以不折叠),后半段不折叠(直接取原长度)这样子转移

 而我们假设前面有一个 M 的话,我们就可以将这整个区间折叠成前面一块再加上后面一块,也就是说我们可以判断一下前面一半和后面一半是不是一样的(当然是长度是偶数的情况),如果是一样的,我们就可以这样转移:

dp[l][r][0]=min(dp[l][t][1]+1)

 这些就是当这个区间内没有 M 的情况,如果有 M 的话就枚举这个 M 在哪里,然后前后一拼接就可以更新了,因为这个区间里面不管有多少个 M 后面都不会再折叠这个处理过的区间了。

 (一开始长度为 1 的 dp[l][r][2] 没有初始化,样例二一直输出 15 ,恼)

点击查看代码
int n;
char s[N];
int dp[N][N][3];
// 0 => no M
// 1 => have M
// 假设在 dp[l][r] 时 l - 1 处已经有了一个 M
// 2 => min(0,1)
lwl hsh[N],p[N];

void init() {
	memset(dp,0x3f,sizeof dp);
	p[0] = 1;
	for (int i = 1; i <= n; i ++) {
		hsh[i] = hsh[i - 1] * P + s[i] - 'a' + 1;
		p[i] = p[i - 1] * P;
	}
	for (int i = 1; i <= n; i ++) {
		dp[i][i][0] = 1;
		dp[i][i][1] = 2;
		dp[i][i][2] = 1;
	}
}

lwl get(int l,int r) {
	return hsh[r] - hsh[l - 1] * p[r - l + 1];
}

int main(){
	scanf("%s",(s + 1));
	n = strlen(s + 1);
	init();
	for (int len = 2; len <= n; len ++) {
		for (int l = 1; ; l ++) {
			int r = l + len - 1;
			if (r > n) break;
			for (int k = l; k < r; k ++)
				// 只有 l - 1 处有 M,所以后面只能不压缩
				dp[l][r][0] = min(dp[l][r][0], dp[l][k][0] + r - k);
			int t = l + (len >> 1) - 1;
			if ((!(len & 1)) && (get(l,t) == get(t + 1,r)))
				dp[l][r][0] = min(dp[l][r][0], dp[l][t][0] + 1);
			for (int k = l; k < r; k ++) {
				// 枚举在哪里放 M
				dp[l][r][1] = min(dp[l][r][1],dp[l][k][2] + dp[k + 1][r][2] + 1);
			}
			dp[l][r][2] = min(dp[l][r][1],dp[l][r][0]);
		}
	}
	fw(dp[1][n][2]);
	ch;
	return 0;
}
posted @   jingyu0929  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示