第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛 题解

题目链接

A - Wasserstein Distance

模拟。从左往右填充每一个,如果某一个格子不足,需要从右边离他最近的有盈余的格子里拿一些来填充;如果某一个格子有盈余,那么多余部分往右扔过去。

/*******************************
	Judge Result : AC 
 *******************************/

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;
int T, n;
long long a[maxn], b[maxn];

int main() {
#ifdef ZHOUZHENTAO
	freopen("test.in", "r", stdin);
#endif

	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		for(int i = 1; i <= n; i ++) {
			scanf("%lld", &a[i]);
		}
		for(int i = 1; i <= n; i ++) {
			scanf("%lld", &b[i]);
			a[i] = a[i] - b[i];
		}
		long long ans = 0;
		int p = 1;
		for(int i = 1; i <= n; i ++) {
			if(a[i] == 0) continue;
			if(a[i] > 0) ans += a[i], a[i + 1] += a[i], a[i] = 0;
			else {
				while(1) {
					while(a[p] <= 0) p ++;
					if(a[i] + a[p] >= 0) {
						ans = ans + (p - i) * (-a[i]);
						a[p] += a[i];
						a[i] = 0;
						break;
					} else {
						a[i] = a[i] + a[p];
						ans = ans + (p - i) * a[p];
						a[p] = 0;
					}
				}
			}
		}
		printf("%lld
", ans);
	}

	return 0;
}

B - 合约数

由于是处理子树问题,所以可以将树转成 dfs 序,然后就变成了区间问题。然后就是询问区间上有几个合约数,莫队操作一下就可以了。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
#define LL long long
 
const int maxn = 2e5 + 10;
vector<int> g[maxn];
int pri[maxn];
 
int T, n, root;
int a[maxn], sz;
int L[maxn], R[maxn];
int h[maxn], nx[maxn], to[maxn], cnt;
int val[maxn], pos[maxn];
int b[maxn], f[maxn];
 
struct point {
    int id, l, r;
}s[maxn];
 
bool cmp(const point& a, const point& b) {
    if(pos[a.l] == pos[b.l]) return a.r < b.r;
    return pos[a.l] < pos[b.l];
}
 
int prime(int x) {
    if(x == 1) return 0;
    for(int i = 2; i * i <= x; i ++) {
        if(x % i == 0) return 0;
    }
    return 1;
}
 
void init() {
    for(int i = 1; i <= 10000; i ++) {
      pri[i] = prime(i);
    }
    for(int i = 4; i <= 10000; i ++) {
        if(pri[i]) continue;
        for(int j = 4; j <= i; j ++) {
            if(i % j) continue;
            if(pri[j]) continue;
            g[i].push_back(j);
        }
    }
}
 
void dfs(int x, int fa) {
    sz ++;
    a[sz] = x;
    L[x] = sz;
    for(int i = h[x]; i != -1; i = nx[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], x);
    }
    R[x] = sz;
}
 
void add(int x, int y) {
    to[cnt] = y;
    nx[cnt] = h[x];
    h[x] = cnt ++;
}
 
void Delete(int x) {
    b[val[a[x]]] --;
}
 
void Insert(int x) {
    b[val[a[x]]] ++;
}
 
int main() {
    init();
 
    scanf("%d", &T);
    while(T --) {
        scanf("%d %d", &n, &root);
 
        int sqr = (int)sqrt(1.0 * n);
        for(int i = 1; i <= n; i ++) {
            pos[i] = i / sqr;
        }
 
        for(int i = 1; i <= 10000; i ++) {
            b[i] = 0;
        }
 
        cnt = 0;
        for(int i = 1; i <= n;i ++) {
            h[i] = -1;
        }
        for(int i = 0; i < n - 1; i ++) {
            int x, y;
            scanf("%d %d", &x, &y);
            add(x, y);
            add(y, x);
        }
        sz = 0;
        dfs(root, -1);
         
        /*
        for(int i = 1; i <= n; i ++) {
            printf("%d ", a[i]);
        }
        printf("
");
 
        for(int i = 1; i <= n; i ++) {
            printf("!!! %d %d %d
", i, L[i], R[i]);
        }
 
        printf("ok
");
        */
         
 
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &val[i]);
        }
 
        for(int i = 1; i <= n; i ++) {
            s[i].id = i;
            s[i].l = L[i];
            s[i].r = R[i];
        }
 
        sort(s + 1, s + 1 + n, cmp);
        /*
        for(int i = 1; i <= n; i ++) {
            printf("q : %d %d %d
", s[i].id, s[i].l, s[i].r);
        }
        */
 
        for(int i = s[1].l; i <= s[1].r; i ++) {
            Insert(i);
        }
 
        f[s[1].id] = 0;
        for(int j = 0; j < g[val[s[1].id]].size(); j ++) {
            f[s[1].id] = f[s[1].id] + b[g[val[s[1].id]][j]];
        }
 
        int left = s[1].l, right = s[1].r;
        for(int i = 2; i <= n; i ++) {
            while(left > s[i].l) left --, Insert(left);
            while(right < s[i].r) right ++, Insert(right);
            while(left < s[i].l) Delete(left), left ++;
            while(right > s[i].r) Delete(right), right --;
            f[s[i].id] = 0;
            for(int j = 0; j < g[val[s[i].id]].size(); j ++) {
                f[s[i].id] = f[s[i].id] + b[g[val[s[i].id]][j]];
            }
        }
 
        /*
        for(int i = 1; i <= n; i ++) {
            printf("!!! %d : %d
", i, f[i]);
        }
        */
 
        long long mod = 1e9 + 7;
        long long ans = 0;
        for(int i = 1; i <= n; i ++) {
            long long tmp = 1LL * i * f[i] % mod;
            ans = (ans + tmp) % mod;
        }
        printf("%lld
", ans);
 
    }
    return 0;
}
 
/*
100
13 10
10 2
3 1
4 11
4 12
4 13
10 4
2 5
10 3
3 8
3 9
2 6
2 7
30 60 50 24 5 10 12 25 10 120 2 3 4
 
*/

C - 序列变换

枚举全排列,计算每一种排列需要的操作次数。从一个排列转换成另一个排列,可以模拟搞。从一个数字转换成另一个数字,只能选择一种方式,因此很简单。

/*******************************
    Judge Result : AC
 *******************************/
 
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;
 
int T, n;
 
int a[maxn], b[maxn];
int cost[20][20];
int p[maxn];
int u[maxn];
int f[maxn];
 
int work(int x, int y) {
    if(x == y) return 0;
    if(x < y) swap(x, y);
    int sum = 0;
    while(1) {
        if(x % 2 == 0) {
            if(x / 2 >= y) sum ++, x = x / 2;
            else return sum + x - y;
        } else {
            if(x == y) return sum;
            else sum ++,  x --;
        }
    }
    return sum;
}
 
int get() {
    int sum = 0;
    for(int i = 1; i <= n; i ++) {
        u[i] = p[i];
    }
    for(int i = 1; i <= n; i ++) {
        if(u[i] == i) continue;
        sum ++;
        for(int j = i + 1; j <= n; j ++) {
            if(u[j] == i) {
                swap(u[i], u[j]);
                break;
            }
        }
    }
    return sum;
}
 
int main() {
#ifdef ZHOUZHENTAO
    freopen("test.in", "r", stdin);
#endif
 
    scanf("%d", &T);
    while(T --) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &b[i]);
        }
 
        for(int i = 1; i <= n; i ++) {
            for(int j = 1; j <= n; j ++) {
                cost[i][j] = work(a[i], b[j]);
                //cout << a[i] << " -> " << b[j] << " : " << cost[i][j] << endl;
            }
        }
 
        for(int i = 1; i <= n; i ++) {
            p[i] = i;
        }
 
        int ans = 2e9;
        do {
            int tmp = get();
            for(int i = 1; i <= n; i ++) {
                tmp = tmp + cost[p[i]][i];
            }
            ans = min(ans, tmp);
        } while(next_permutation(p + 1, p + 1 + n));
 
        printf("%d
", ans);
    }
 
    return 0;
}

D - 数字游戏

大致思路为如果无论 $n_1$ 为多少,都有一个 $n_2$ 能够找到,使得结果是 mod 的倍数,则先手输;否则后手输。由于 mod 范围不大,所以可以枚举一下 $n_2$ 的位数,然后暴力枚举处理,只要枚举 mod 范围内即可,因为取模之后的数不会超过 mod。

E - 小Y吃苹果

答案是 2 的 $n$ 次方。

#include <stdio.h>
 
int main()
{
    int n;
    scanf("%d", &n);
    printf("%d
", 1 << n);
    return 0;
}

F - 1 + 2 = 3?

找找规律可以发现满足条件的数字是二进制上没有相邻的 1。因此可以二分答案,然后数位 dp 计算方案数。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
#define LL long long
 
LL dp[110][2];
LL a[110];
LL n;
 
LL dfs(int len,int sta,bool limit)
{
    if(len<0)
        return 1;
    if(dp[len][sta]!=-1&&!limit)
        return dp[len][sta];
    int up=limit?a[len]:1;
    LL ans=0;
    for(int i=0; i<=up; i++)
    {
        if(sta&&i==1)
            continue;
        ans+=dfs(len-1,i==1,limit&&i==up);
    }
    return limit?ans:dp[len][sta]=ans;
}
 
LL solve(LL x)
{
    memset(dp,-1,sizeof dp);
    int cnt=0;
    while(x>0)
    {
        a[cnt++]=x%2;
        x/=2;
    }
    return dfs(cnt-1,0,1);
}
 
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%lld", &n);
        LL l = 1, r = 1e18;
        LL mid, ans;
        while(l <= r)
        {
            mid = (l + r) / 2;
            LL t = solve(mid) - 1;
            if(t < n)
            {
                l = mid + 1;
            }
            else
            {
                ans = mid;
                r = mid - 1;
            }
        }
        printf("%lld
", ans);
    }
    return 0;
}

G - 小Y做比赛

暂时不会做

H - 小Y与多米诺骨牌

先处理出选择 $i$ 位置往左倒,最左会使得 $L_i$ 也到下;往右倒,最右会使得 $R_i$ 也倒下。

然后进行 dp,$dp_i$ 表示 $[1, i]$ 都倒下需要的最少操作次数。有两种途径,一种是 $[1, j]$ 先倒下,然后选择 $i$ 往左倒,使得 $[j+1,i]$ 都倒下;另一种是 $[1, j]$ 先倒下,然后选择 $j$ 向右倒,使得 $[j+1,i]$ 都倒下。两种都可以用线段树来维护来得到最优解。

/*******************************
 Judge Result :
*******************************/
 
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 1e5 + 10;
const int INF = 1e6;
int x[maxn], y[maxn];
int T, n;
int L[maxn], R[maxn];
int s[2][4 * maxn];
int dp[maxn];
 
vector<int> g[maxn];
 
void build(int flag, int val, int l, int r, int rt) {
    s[flag][rt] = val;
    if(l == r) return ;
    int mid = (l + r) / 2;
    build(flag, val, l, mid, 2 * rt);
    build(flag, val, mid + 1, r, 2 * rt + 1);
}
 
void update(int flag, int op, int pos, int val, int l, int r, int rt) {
    if(l == r) {
        s[flag][rt] = val;
        return;
    }
    int mid = (l + r) / 2;
    if(pos <= mid) update(flag, op, pos, val, l, mid, 2 * rt);
    else update(flag, op, pos, val, mid + 1, r, 2 * rt + 1);
    if(op == 0) s[flag][rt] = min(s[flag][2 * rt], s[flag][2 * rt + 1]);
    else s[flag][rt] = max(s[flag][2 * rt], s[flag][2 * rt + 1]);
}
 
int getmin(int flag, int L, int R, int l, int r, int rt) {
    if(L <= l && r <= R) {
        return s[flag][rt];
    }
    int mid = (l + r) / 2;
    int left = INF, right = INF;
    if(L <= mid) left = getmin(flag, L, R, l, mid, 2 * rt);
    if(R > mid) right = getmin(flag, L, R, mid + 1, r, 2 * rt + 1);
    return min(left, right);
}
 
int getmax(int flag, int L, int R, int l, int r, int rt) {
    if(L <= l && r <= R) {
        return s[flag][rt];
    }
    int mid = (l + r) / 2;
    int left = 0, right = 0;
    if(L <= mid) left = getmax(flag, L, R, l, mid, 2 * rt);
    if(R > mid) right = getmax(flag, L, R, mid + 1, r, 2 * rt + 1);
    return max(left, right);
}
 
int main() {
#ifdef ZHOUZHENTAO
  freopen("test.in", "r", stdin);
#endif
 
    scanf("%d", &T);
    while(T --) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d%d", &x[i], &y[i]);
        }
 
        build(0, INF, 1, n, 1);
        L[1] = 1;
        update(0, 0, 1, 1, 1, n, 1);
        for(int i = 2; i <= n; i ++) {
            update(0, 0, i, i, 1, n, 1);
            int left = 1, right = i, ans = -1;
            while(left <= right) {
                int mid = (left + right) / 2;
                if(x[mid] > x[i] - y[i]) ans = mid, right = mid - 1;
                else left = mid + 1;
            }
            L[i] = getmin(0, ans, i, 1, n, 1);
            update(0, 0, i, L[i], 1, n, 1);
        }
 
        build(0, 0, 1, n, 1);
        R[n] = n;
        update(0, 1, n, n, 1, n, 1);
        for(int i = n - 1; i >= 1; i --) {
            update(0, 1, i, i, 1, n, 1);
            int left = i, right = n, ans = -1;
            while(left <= right) {
                int mid = (left + right) / 2;
                if(x[mid] < x[i] + y[i]) ans = mid, left = mid + 1;
                else right = mid - 1;
            }
            R[i] = getmax(0, i, ans, 1, n, 1);
            update(0, 1, i, R[i], 1, n, 1);
        }
 
    /* 
        for(int i = 1; i <= n; i ++) {
            cout << i << " " << L[i] << " " << R[i] << endl;
        }
        */
 
        for(int i = 1; i <= n; i ++) {
            g[i].clear();
        }
        for(int i = 1; i <= n; i ++) {
            g[R[i]].push_back(i);
        }
 
        build(0, INF, 0, n, 1);
        build(1, INF, 0, n, 1);
 
        update(0, 0, 0, 0, 0, n, 1);
        update(1, 0, 0, 0, 0, n, 1);
        for(int i = 1; i <= n; i ++) {
            dp[i] = getmin(1, L[i] - 1, i - 1, 0, n, 1) + 1;
            dp[i] = min(dp[i], getmin(0, 0, i - 1, 0, n, 1) + 1);
            update(1, 0, i, dp[i], 0, n, 1);
          update(0, 0, i, dp[i], 0, n, 1);
            for(int j = 0; j < g[i].size(); j ++) {
                update(0, 0, g[i][j] - 1, INF, 0, n, 1);
            }
        }
/*
        for(int i = 1; i <= n ; i ++) {
            printf("dp[%d] = %d
", i, dp[i]);
        }
*/
        printf("%d
", dp[n]);
    }
 
    return 0;
}

I - 二数

构造一下最大的小于等于 $n$ 的二数,以及最小的大于等于 $n$ 的二数,取小的就是答案。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
#define LL long long
 
char s[1000111];
 
int main() {
    int T;
    scanf("%d", &T);
    while (~scanf("%s", &s)) {
        int k=strlen(s);
        if(s[0]=='1'&&k==1){
        printf("0
");
        continue;
        }
        int flag=0;
        for(int i=0;i<k;i++){
            if((s[i]-'0')%2==1){
                if(s[i]=='9'){
                flag=-1;
                break;
                }
                for(int j=i+1;j<k;j++){
                    if(s[j]>'4'){
                    flag=1;
                    break;
                    }
                    else if(s[j]<'4'){
                    flag=-1;
                    break;
                    }
                }
                if(flag!=0) break;
                flag=-1;
                break;
            }
        }
 
 
        if(flag==0){
            printf("%s",s);
        }
        else if(flag==-1){
            int i=0;
            for(i=0;i<k;i++){
                if((s[i]-'0')%2==1){
                    if(s[i]>'1'||i>0)
                    printf("%c",s[i]-1);
                    break;
                }
                printf("%c",s[i]);
            }
            i++;
            for(;i<k;i++){
                printf("%c",'8');
            }
        }
        else if(flag==1){
            int i=0;
            for(i=0;i<k;i++){
                 
                if((s[i]-'0')%2==1){
                    printf("%c",s[i]+1);
                    break;
                }
                printf("%c",s[i]);
            }
            i++;
            for(;i<k;i++){
                printf("%c",'0');
            }
        }
        printf("
");
    }
    return 0;
}

J - 小Y写文章

二分答案 $x$,需要验证答案小于等于 $x$ 能否做到。验证:可以考虑为有 $n + 1$ 个空位,需要 $m$ 个物品去填充。如果某个物品可以放在某个位置,就连边,容量为 1。需要额外增加一个节点,如果某个位置可以不放物品,这个节点就和那个位置连边。看看网络最大流是不是为 $n+1$ 即可。如果是,则说明存在放置的方案。

/*******************************
    Judge Result : AC
 *******************************/
 
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 500 + 10;
const int INF = 0x7FFFFFFF;
struct Edge
{
    int from, to, cap, flow;
    Edge(int u, int v, int c, int f) :from(u), to(v), cap(c), flow(f){}
};
vector<Edge>edges;
vector<int>G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
int S, T;
 
void init()
{
    for (int i = 0; i < maxn; i++)
        G[i].clear();
    edges.clear();
}
void AddEdge(int from, int to, int cap)
{
    //  cout << from << " -> " << to << "    " << cap << endl;
    edges.push_back(Edge(from, to, cap, 0));
    edges.push_back(Edge(to, from, 0, 0));
    int w = edges.size();
    G[from].push_back(w - 2);
    G[to].push_back(w - 1);
}
bool BFS()
{
    memset(vis, 0, sizeof(vis));
    queue<int>Q;
    Q.push(S);
    d[S] = 0;
    vis[S] = 1;
    while (!Q.empty())
    {
        int x = Q.front();
        Q.pop();
        for (int i = 0; i<G[x].size(); i++)
        {
            Edge e = edges[G[x][i]];
            if (!vis[e.to] && e.cap>e.flow)
            {
                vis[e.to] = 1;
                d[e.to] = d[x] + 1;
                Q.push(e.to);
            }
        }
    }
    return vis[T];
}
int DFS(int x, int a)
{
    if (x == T || a == 0)
        return a;
    int flow = 0, f;
    for (int &i = cur[x]; i<G[x].size(); i++)
    {
        Edge e = edges[G[x][i]];
        if (d[x]+1 == d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0)
        {
            edges[G[x][i]].flow+=f;
            edges[G[x][i] ^ 1].flow-=f;
            flow+=f;
            a-=f;
            if(a==0) break;
        }
    }
    if(!flow) d[x] = -1;
    return flow;
}
int dinic(int s, int t)
{
    int flow = 0;
    while (BFS())
    {
        memset(cur, 0, sizeof(cur));
        flow += DFS(s, INF);
    }
    return flow;
}
 
int a[maxn], b[maxn];
int n, m;
 
int check(int limit) {
    init();
    int s = 0;
    int t = n + m + 2;
 
    S = n + m + 3;
    T = n + m + 4;
 
    AddEdge(S, s, INF);
    AddEdge(t, T, INF);
 
    AddEdge(n + m + 5, 0 + 1 + m, 1);
    AddEdge(n + m + 5, n + 1 + m, 1);
 
    for(int i = 1; i < n ; i ++) {
        if(abs(a[i] - a[i + 1]) <= limit) {
            AddEdge(n + m + 5, i + 1 + m, 1);
        }
    }
    AddEdge(s, n + m + 5, n + 1 - m);
    for(int i = 1; i <= m; i ++) {
        AddEdge(s, i, 1);
    }
    for(int i = 1; i <= m; i ++) {
        for(int j = 0; j <= n; j ++) {
            if(j == 0) {
                if(abs(b[i] - a[1]) <= limit) {
                    AddEdge(i, j + 1 + m, 1);
                }
            } else if(j < n) {
                if(abs(b[i] - a[j]) <= limit && abs(b[i] - a[j + 1]) <= limit) {
                    //      cout << i << "  " << j << endl;
                    AddEdge(i, j + 1 + m, 1);
                }
            } else {
                if(abs(b[i] - a[n]) <= limit) {
                    AddEdge(i, j + 1 + m, 1);
                }
            }
        }
    }
    for(int j = 0; j <= n; j ++) {
        AddEdge(j + 1 + m, t, 1);
    }
    if(dinic(S, T) == n + 1) return 1;
    return 0;
}
 
int main() {
#ifdef ZHOUZHENTAO
    freopen("test.in", "r", stdin);
#endif
    int T;
    scanf("%d", &T);
    while(T --) {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= m; i ++) {
            scanf("%d", &b[i]);
        }
        int left = 0, right = 2e9, ans;
        while(left <= right) {
            int mid = (left + right) / 2;
            if(check(mid)) ans = mid, right = mid - 1;
            else left = mid + 1;
        }
        printf("%d
", ans);
    }
    return 0;
}

K - 树上最大值

有个东西叫线性基:支持插入元素,合并两个集合,计算集合中选一些数异或值最大的方法。所有操作复杂度均为 $O(log(value))$。有了这个方法这题就很简单了。

枚举所有子树,A 集合在这个子树中,B 集合在子树以外部分。因此可以转化为区间问题,子树内用树形dp加上上面那个方法做就好了,子树外就是两个区间的并集,用前缀和后缀的并集即可。

/*******************************
    Judge Result : AC
 *******************************/
 
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 1e5 + 2;
const int INF = 0x7FFFFFFF;
 
struct Base{ 
    int a[30];
    void init(){for(int i=0;i<30;i++)a[i]=0;}
    void up(int &a, int b) {if(b>a)a=b;}
    void ins(int x){for(int i=29;~i;i--)if(x>>i&1){if(a[i])x^=a[i];else{a[i]=x;break;}}}
    int ask(){
        int t=0;
        for(int i=29;~i;i--)up(t,t^a[i]);
        return t;
    }
};
 
Base merge(const Base& b1, const Base& b2) {
    Base res = b1;
    for(int i = 0; i < 30; i ++) {
        if(b2.a[i]) res.ins(b2.a[i]);
    }
    return res;
}
 
int A[maxn], sz;
int L[maxn], R[maxn];
int h[maxn], nx[2 * maxn], to[2 * maxn], cnt;
int n, val[maxn];
Base node[maxn], pre[maxn], suf[maxn];
 
void dfs(int x, int fa) {
    sz ++;
    A[sz] = x;
    L[x] = sz;
    node[x].init();
    node[x].ins(val[x]);
    for(int i = h[x]; i != -1; i = nx[i]) {
        if(to[i] == fa) continue;
        dfs(to[i], x);
        node[x] = merge(node[x], node[to[i]]);
    }
    R[x] = sz;
}
 
void add(int x, int y) {
    to[cnt] = y;
    nx[cnt] = h[x];
    h[x] = cnt ++;
}
 
int main() {
#ifdef ZHOUZHENTAO
    freopen("test.in", "r", stdin);
#endif
 
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) {
        h[i] = -1;
        scanf("%d", &val[i]);
    }
    for(int i = 0; i < n - 1; i ++) {
        int x, y;
        scanf("%d %d", &x, &y);
        add(x, y);
        add(y, x);
    }
    dfs(1, -1);
    pre[0].init();
    for(int i = 1; i <= n; i ++) {
        pre[i] = pre[i - 1];
        pre[i].ins(val[A[i]]);
    }
    suf[n + 1].init();
    for(int i = n; i >= 1; i --) {
        suf[i] = suf[i + 1];
        suf[i].ins(val[A[i]]);
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++) {
        Base tmp = node[i];
        Base left, right;
        left.init();
        right.init();
        if(L[i] != 1) left = pre[L[i] - 1];
        if(R[i] != n) right = suf[R[i] + 1];
        ans = max(ans, tmp.ask() + merge(left, right).ask());
    }
    printf("%d
", ans);
    return 0;
}

L - K序列

$dp_{i,j}$ 表示前 $i$ 个数,和对 $mod$ 取模的结果为 $j$ 的子序列的最长长度。开个滚动数组就好了。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
#define LL long long
 
int num[100111];
int dp[2][10001111];
int n, k;
 
int main() {
  while (~scanf("%d %d", &n, &k)) {
    for (int i = 0; i < n; ++ i) {
      scanf("%d", num + i);
      num[i] %= k;
    }
    int wei = 0;
    memset(dp, -1, sizeof dp);
    dp[wei][0] = 0;
    for (int i = 0; i < n; ++ i) {
        for (int j = 0; j < k; ++ j) {
          dp[wei ^ 1][(j + num[i]) % k] = dp[wei][(j + num[i]) % k];
          if (dp[wei][j] != -1)
            dp[wei ^ 1][(j + num[i]) % k] = max(dp[wei][(j + num[i]) % k], dp[wei][j] + 1);
        }
        wei ^= 1;
    }
    printf("%d
", dp[wei][0]);
  }
  return 0;
}
原文地址:https://www.cnblogs.com/zufezzt/p/8858509.html