Codeforces Round #572 (Div. 1)

Codeforces Round #572 (Div. 1)
A1:
观察发现,只要有度为2的点,就会导致这个点关联的两条边的权重相等,此时为NO,否则是YES。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
vector<int> adj[N];
int n;
bool ok;
void dfs(int u, int p) {
    if(p!=0&&adj[u].size()==2) ok=false;
    for(int v : adj[u]) {
        if(v==p) continue;
        dfs(v, u);
    }
}
int main() {
    scanf("%d", &n);
    for(int i=1; i<n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    ok = true;
    for(int i=1; i<=n; ++i) {
        if(adj[i].size()==1) {
            dfs(i, 0);
            break;
        }
    }
    if(ok) puts("YES");
    else puts("NO");
}

A2:
首先观察可以发现,对于一个点 p 1 p_1 p1 ,它的两个儿子是 p 2 p_2 p2 p 3 p_3 p3 ,它的父亲是 p 4 p_4 p4 ,且 p 2 p_2 p2 p 3 p_3 p3 是叶子,设 ( p 1 , p 4 ) (p_1, p_4) (p1,p4) 这条边为 x x x ,值为 w x w_x wx ,设 ( p 1 , p 2 ) (p_1, p_2) (p1,p2) 这条边为 y y y ,值为 w y w_y wy,设 ( p 1 , p 3 ) (p_1,p_3) (p1,p3) 这条边为 z z z,值为 w z w_z wz 。那么我们总可以通过 p 2 p_2 p2 p 3 p_3 p3 和根 r t rt rt (假设我们从一个度为 1 1 1 的点开始dfs的)这三个叶子将 x x x y y y 的权重变为 w y w_y wy ,将 z z z 的权重变为 0 0 0。那么接下来我们可以对每个儿子进行上述操作,最后得到一个值为 w y w_y wy 的儿子边和其它值为 0 0 0 的儿子边。然后我们回溯到父亲,由于之前的 x x x y y y 的权重是相等的,因此我们可以把 ( p 2 , p 4 ) (p_2,p_4) (p2,p4) 这条路径看做一条权重为 w y w_y wy 的边。然后对于它的每个儿子,同样经过dfs后也得到一条被边权相等的路径。因此此时又可以看做得到一条有边权相等的路径加若干条权重为 0 0 0 的路径。一直到根,得到一条有权重的路径,最后我们将这条边权相等的路径清零即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1007;
struct Edge {
    int u, v, x;
};
vector<Edge> ans;
vector<Edge> adj[N];
int c[N]; //底层边剩余的权重。
int lf[N]; //底层边对应的叶子
int rt;
bool ok;
void check(int u, int p) {
    if(p!=0&&adj[u].size()==2) ok=false;
    for(Edge e : adj[u]) {
        int v = e.v;
        if(v==p) continue;
        check(v, u);
    }
}
int dfs(int u, int p, int w) {
    // w是上面那条边的原始值
    // diff:返回值是上层边需增加的值
    int diff = 0;
//    printf("dfs: %d\n", u);
    if(adj[u].size()==1) {
        lf[u] = u;
        c[u] = w;
        return 0;
    }
    bool first = true;
    int fu;
    for(Edge e : adj[u]) {
        if(e.v == p) continue;
        int v = e.v;
        int d = dfs(v, u, e.x);
        diff += d;
        w += d;
        if(first) fu = v;
        else {
//            printf("process %d %d, traingle=%d %d %d\n", u, v, w, c[fu], c[v]);
            int origin_cfu = c[fu];
            int target = (w+c[fu]-c[v])/2;
            ans.push_back({rt, lf[v], target-w});
            ans.push_back({lf[fu], lf[v], target-c[fu]});
            diff += target-w;
            w=c[fu]=target;
            ans.push_back({lf[fu], rt, origin_cfu-c[fu]});
            diff += origin_cfu-c[fu];
            c[fu]=w=origin_cfu;
        }
        first = false;
    }
    c[u]=w;
    lf[u] = lf[fu];
    return diff;
}
int main() {
    int n;
    scanf("%d", &n);
    for(int i=1; i<n; ++i) {
        int u, v, x;
        scanf("%d%d%d", &u, &v, &x);
        adj[u].push_back({u, v, x});
        adj[v].push_back({v, u, x});
    }
    ok = true;
    for(int i=1; i<=n; ++i) {
        if(adj[i].size()==1) {
            check(i, 0);
            break;
        }
    }
    if(!ok) {
        puts("NO");
        exit(0);
    }
    for(int i=1; i<=n; ++i) {
        if(adj[i].size()==1) {
            rt=i;
            dfs(adj[i][0].v, i, adj[i][0].x);
            ans.push_back({i, lf[adj[i][0].v], -c[adj[i][0].v]});
            break;
        }
    }
    puts("YES");
    printf("%d\n", (int)ans.size());
    for(Edge e : ans){
        printf("%d %d %d\n", e.u, e.v, -e.x);
    }
    return 0;
}

B:
找出所有 ( a i + a j ) ( a i 2 + a j 2 ) ≡ k &VeryThinSpace; m o d &VeryThinSpace; p (a_i + a_j)(a_i^2 + a_j^2) \equiv k \bmod p (ai+aj)(ai2+aj2)kmodp 条件满足的 ( i , j ) (i,j) (i,j) 数量。因为与 i i i , j j j 相关的变量在等式的同一边,因此无法通过存在 set 里进行维护。两边乘上 ( a i − a j ) (a_i-a_j) (aiaj) 后得到 a i 4 − k a i = a j 4 − k a j a_i^4-ka_i=a_j^4-ka_j ai4kai=aj4kaj 此时 i i i j j j 相关的变量在等式两边了,因此可以通过将已遍历过的 j j j 存在 set 里维护即可。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    int n;
    ll p, k;
    scanf("%d%I64d%I64d", &n, &p, &k);
    ll ans = 0;
    multiset<ll> s;
    for(int i=0; i<n; ++i) {
        ll a;
        scanf("%I64d", &a);
        ll res = 0;
        res =(p-k*a%p)%p;
        a=a*a%p;
        a=a*a%p;
        res = (res+a)%p;
//        printf("pp %I64d\n", res);
        ans += s.count(res);
        s.insert(res);
    }
    printf("%I64d\n", ans);
}

F:
首先想到可以通过算出每个美丽值对应的序列数量,进一步可以发现,设美丽值大于等于 x x x 的序列数量为 p x p_x px ,设 a a a 数组的最大值为 m a x ( a ) max(a) max(a) ,那么答案是 p 1 + p 2 + . . . + p m a x ( a ) p_1+p_2+...+p_{max(a)} p1+p2+...+pmax(a) 。我们可以将数组排序后设 d p [ i ] [ j ] dp[i][j] dp[i][j] i i i 位置,选 j j j 个作为序列元素的满足条件的序列数量,设 a l a_l al 是满足 a i − a l ≥ p x a_i-a_l \geq p_x aialpx 的第一个值,那么答案从 a 1 . . . a l a_1...a_l a1...al 的dp值转移而来,因为随着 x x x 的递增 a l a_l al 是单调减的,因此维护一个指针就可以实现 O ( 1 ) O(1) O(1) 的转移。因此dp的复杂度是 O ( n k ) O(nk) O(nk)
进一步观察可以发现,美丽值是不能超过 m a x ( a ) ( k − 1 ) \frac{max(a)}{(k-1)} (k1)max(a) 的,因为有 ( k − 1 ) (k-1) (k1) 个不同的差。如果超过这个值,序列的元素数量肯定小于 k k k ,这种情况不合法。因此总的复杂度为 O ( m a x ( a ) k − 1 n k ) O(\frac{max(a)}{k-1}nk) O(k1max(a)nk) 。即 O ( m a x ( a ) n ) O(max(a)n) O(max(a)n)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 998244353;
const int N = 1e3+7;
int a[N], lb[N];
ll dp[N][N];
// dp[i][j]=1~i中长度为j的合法序列数量
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    int mx=0;
    for(int i=1; i<=n; ++i) {
        scanf("%d", &a[i]);
        mx = max(mx, a[i]);
        lb[i] = i;
    }
    mx /= (k-1);
//    printf("mx: %d\n", mx);
    sort(a+1, a+n+1);
    ll ans = 0;
    for(int p=1; p<=mx; ++p) {
        ll res = 0;
        for(int i=1; i<=n; ++i) dp[i][1] = i;
        for(int i=1; i<=n; ++i) {
            while(lb[i]>0&&a[i]-a[lb[i]]<p) --lb[i];
            for(int j=k; j>=2; --j) {
                dp[i][j] = dp[lb[i]][j-1] + dp[i-1][j];
                dp[i][j] %= mod;
//                printf("p=%d, lb=%d dp[%d][%d]=%I64d\n", p, lb[i], i, j, dp[i][j]);
            }
        }
        ans = (ans+dp[n][k])%mod;
    }
    printf("%I64d\n", ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值