USACO 2020 February Contest, Gold

USACO 2020 February Contest, Gold

图片懒得上传了,如果影响阅读可以看个人公开笔记
另外就是之前接近一年没登陆,所以消息都没看到,抱歉了。

测试地址

Problem 1. Delegation

题面描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10myv8p6-1611135874428)(https://i.loli.net/2021/01/20/Tt8W5LVDlkUjBEy.png)]

解题思路

本题是求每次挤奶的最早时间,由于某次挤奶时间可能有依赖,所以可以将依赖看作边,即 a 比 b 至少晚 z 天,就建一条 b --> a 的边,边权为 z ,接下来只要找到所有的入度为 0 的点,从他们出发更新其它所有点的最大路径即可。

根据题意可知我们建立的图中不会有环,但是一个点可能被重复访问多次,而一个点一旦被修改,那么从他出发所有的终点都需要更新,于是可以采用深搜或者广搜,采用深搜剪枝有些麻烦,于是我后来又改成广搜了,改成广搜需要一个标记数组记录待更新节点是否已经在队列中了,如果已经在就无需再次压入了。

但是需要注意的是,一个点第一次被访问时无论是否被更新都要压入队列的,否则无法更新后面的点,因此还需要一个标记数组 vis2 来记录某个点是否被第一次访问。

代码示例
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,c;
const int N = 1e5+10;
const int M = 10*N;
int head[N],ver[M],nex[M],edge[M],tot = 1;
int deg[N];
void addEdge(int x,int y,int z){
    ver[++tot] = y; edge[tot] = z;
    nex[tot] = head[x]; head[x] = tot;
    deg[y]++;
}
int s[N];
queue<int> que;
int vis[N],vis2[N];

void solve(){
    for(int i = 1;i <= n;i++) scanf("%d",s+i);
    for(int i = 1,x,y,z;i <= c;i++){
        scanf("%d%d%d",&x,&y,&z);
        addEdge(x,y,z);
    }

    for(int i = 1;i <= n;i++)
        if(!deg[i]) que.push(i),vis[i] = 1;
    
    while(que.size()){
        int x = que.front(); que.pop();
        vis[x] = 0,vis2[x] = 1;
        for(int i = head[x];i;i = nex[i]){
            int y = ver[i], z = edge[i];
            if(s[x]+z >= s[y]){
                if(!vis[y]) que.push(y),vis[y] = 1;
                s[y] = s[x]+z;
            }
            if(!vis2[y] && !vis[y]) que.push(y),vis[y] = 1;
        }
    }
    for(int i = 1;i <= n;i++) printf("%d\n",s[i]);
}
int main(){
    scanf("%d%d%d",&n,&m,&c);
    solve();
    return 0;
}

Problem 2. Help Yourself

题面描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KxTuvHPz-1611135874432)(https://i.loli.net/2021/01/20/Mkmo8xnR12twOC9.png)]

解题思路

假设对于第 i 个线段,在其前面(坐标轴意义的前) a 条线段与其相交,那么该线段对答案的贡献是 2 n − 1 − a 2^{n-1-a} 2n1a ,因为其它 n-1-a 条线段都与其不相交,所以这 a 条线段对这 n-1-a 条线段的所有子集的复杂度之和为 2 n − 1 − a 2^{n-1-a} 2n1a

这里求相交的线段数量采用差分+前缀和的思路,在求一维坐标轴上线段相交常用这种方法。

代码示例
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
const int MOD = 1e9+7;
int n,ans = 0;
int sum[N],l[N],r[N],pw2[N];
void solve(){
    for(int i = 1;i <= n;i++)
        cin >> l[i] >> r[i];
    pw2[0] = 1;
    for(int i = 1;i < n;i++) pw2[i] = pw2[i-1]*2%MOD;//预处理出 2 的幂
    for(int i = 1;i <= n;i++){
        sum[l[i]]++, sum[r[i]]--;                    //进行差分
    }
    for(int i = 1;i <= 2*n;i++) sum[i] += sum[i-1];   //求前缀和
    for(int i = 1;i <= n;i++) ans = (ans + pw2[n-1-sum[l[i]-1]])%MOD;
    cout << ans << endl;
}
int main(){
    scanf("%d",&n);
    solve();
    return 0;
}

Problem 3. Delegation

题面描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctSpL4Yq-1611135874435)(https://i.loli.net/2021/01/20/hNJi51tCTWpq4BV.png)]

解题思路

这道题的难点在于如何判断一个 k 是否合法。

可以通过树上 dp 的做法来判断能否分为若干条长度为 k 的链。不应该将注意力放在边上,而是应该放在点上。无需关注每颗子树如何分割,只需关系到底能不能分割。如果一个子树有 a 个节点,那么这个子树内一定可以分出 a/k 条长度为 k 的链,同时剩下 a%k 条边。所以只需要考虑子树内分割后剩下多少条边,再考虑子树外剩下多少条边,然后看看能否配对即可。

显然树上 dp 可以通过 dfs 来实现,通过一次树上 dp 来求出所需的数据,而后只需 o(N) 一次判断即可。

解决了如何判断 k 是否合法,剩下的就简单了,因为我们很容易就能分析出来,若 k 不是 n-1 的因子,那就无论如何都不能均等分割的,所以实际上最多只需要执行 m 次 check(),其中 m 是 n-1 的因子数目。所以总的时间复杂度为 O(mn)。

代码示例
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int MOD = 1e9+10;
const int M = 2*N;
int n;
int head[N],ver[M],nex[M],tot = 1;
void addEdge(int x,int y){
    ver[++tot] = y, nex[tot] = head[x]; head[x] = tot;
}
vector<int> num[N];
int sub[N];     // sub[x] 子树 x 的节点个数
void dfs(int x,int fa){
    sub[x] = 1;
    for(int i = head[x];i ;i = nex[i]){
        int y = ver[i];
        if(y == fa) continue;
        dfs(y,x);
        sub[x] += sub[y];
        num[x].push_back(sub[y]);
    }
    if(sub[x] != n) num[x].push_back(n-sub[x]);
}
int cur[N];
bool check(int k){
    if((n-1)%k) return false;
    for(int i = 0;i < k;i++) cur[i] = 0;
    for(int i = 1;i <= n;i++){
        int cnt = 0;
        for(auto &t:num[i]){
            int z = t%k;
            if(z == 0) continue;
            if(cur[k-z]) cur[k-z]--,cnt--;
            else cur[z]++,cnt++;
        }
        if(cnt) return false;
    }
    return true;
}
void solve(){
    for(int i = 1,x,y;i < n;i++){
        scanf("%d%d",&x,&y);
        addEdge(x,y); addEdge(y,x);
    }
    dfs(1,0);
    for(int i = 1;i < n;i++) cout << check(i);
    cout << endl;
}
int main(){
    scanf("%d",&n);
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值