(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(1)

Contest Login

1002 City Upgrading

树形dp

利用dfs,从顶点往下搜,先搜到叶子节点,然后回溯,最下面的点赋值,然后往上递推

三种状态:

f[now][0]表示now节点没有放置路由器,也没有被其它路由器覆盖,那么它的子节点不能放置路由器,但需要被覆盖,所以f[now][0]=f[now][0]+f[v][2](为什么它的子节点要被覆盖呢?因为f[i][0]就表示i节点没有路由器也没有被覆盖,然后它的子孙全部被覆盖的最小代价,所以每次都保证子节点全部被覆盖,一层一层往上推,才能得到其子孙全部被覆盖)

f[now][1]表示now节点放置了路由器,那么它的子节点就全部被覆盖了,那么它的子节点是哪种状态都可以,只要在三种状态中取代价最小的就行,所以f[now][1]=f[now][1]+min(f[v][0],min(f[v][1],f[v][2]))

f[now][2]表示now节点没有放置路由器,但是被覆盖到了,可以由now节点没有放置路由器也没有被覆盖到,然后在子节点放置路由器转移过来,也可以由now节点未放置路由器但被覆盖到,然后子节点被覆盖到,子节点可以放置路由器也可以不放置路由器转移过来,即f[now][2]=min(f[now][2]+min(f[v][1],f[v][2]),f[now][0]+f[v][1])

然后add(f[now][0],f[v][2])要放在f[now][2]=min(f[now][2]+min(f[v][1],f[v][2]),f[now][0]+f[v][1])的后面,否则f[now][2]转移的时候就可能加两个f[v][1]了 

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
#define INF 100000000000000
using namespace std;
typedef long long LL;
const int N=1e5+10,M=2*N;
int a[N];
vector<vector<int>>e(M);
//f[i][0]表示i点没有安放路由器,i不在路由器的覆盖范围内,i的子孙全部被路由器覆盖的最小代价
//f[i][1]表示i点安放路由器,i的子孙全部被路由器覆盖的最小代价
//f[i][2]表示i点没有安放路由器,但是i在路由器的覆盖范围内(可以由它的一个儿子安放)
//,i的子孙全部被路由器覆盖的最小代价
LL f[M][3];
int p[N];//存储父节点
void add(LL &x,LL y){
    x+=y;
    if(x>=INF) x=INF;
}
void dfs(int now){
    //初始化
    f[now][0]=0;
    f[now][1]=a[now];f[now][2]=INF;
    for(auto v:e[now]){
        if(v==p[now]) continue;
        p[v]=now;//标记父节点
        dfs(v);
        f[now][2]=min(f[now][2]+min(f[v][1],f[v][2]),f[now][0]+f[v][1]);
        add(f[now][1],min(f[v][0],min(f[v][1],f[v][2])));
        add(f[now][0],f[v][2]);
    }
}
void solve()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        e[i].clear();
    }
    for(int i=0;i<n-1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1);
    LL res=min(f[1][1],f[1][2]);
    printf("%lld\n",res);
}
signed main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    solve();
    return 0;
}

1005 Cyclically Isomorohic

注意,scanf和cin加速不能一起用

先将字符串变成原来的两倍,这样的话,遍历每一个字符,往后取m位组成的字符串可以遍历所有的循环右移字符串

然后找到字典序最小的字符串的起点,然后从起点开始取m个字符组成一个字符串

将该字符串映射成唯一的哈希值

所以只要两个字符串哈希值一样,那么它们就可以互相循环右移得到

字符串哈希的话,一般mod取1e9+7,或1e9+3,p取131或13331 

那么如何找字典序最小的字符串的起点呢?

两个指针l和r,l一开始指向第一个位置,r一开始指向第二个位置(l和r分别代表了枚举的字符串的起点)

l最终指向找到的字典序最小的字符串的起点

两种情况:

1.字符串的每个字符都不相同

那么每次k都只能等于0

然后s[l]和s[r]进行比较,如果s[l]小于s[r],说明以l为起点的字符串小于以r为起点的字符串,所以r++,r继续往后;如果s[l]大于s[r],那么说明以r为起点的字符串小于以l为起点的字符串,所以l更新为r,l始终要指向找到的字典序最小的字符串的起点,然后r就从l后面接着找,直到r大于m就结束寻找

2.字符串中含有相同的字符

如图,l一开始指向1,r指向2,然后s[1]s[5],所以说明以l为起点的字符串的第二位大于以r为字符串的第二位,说明以l为起点的字符串字典序大于以r为起点的字符串字典序,那么就更新l为r即为4(因为l始终要找字符串字典序最小的起点),然后r就取max(r,l+k)即为4,l+k是因为字符串的起点到l+k都已经遍历过了,r是因为后面字符串的起点已经遍历到了r,最后r++每次循环都会执行的,就是接着往后找

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=2e5+10,mod=1e9+7;
char s[N]; 
int a[N];
int n,m,q;
void solve()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=m+1;j<=m+m;j++) s[j]=s[j-m];//s变为原来的两倍,s[0]是空的,字符串从s[1]开始
        int l=1,r=2;
        while(r<=m){
            for(int k=0;k<m;k++){
                if(s[l+k]<s[r+k]){
                    r+=k;
                    break;
                }
                if(s[l+k]>s[r+k]){
                    int tmp=l;
                    l=r;
                    r=max(r,tmp+k);
                    break;
                }
            }
            r++;
        }
        a[i]=0;
        for(int j=0;j<m;j++) a[i]=(1311ll*a[i]+s[l+j]-'a'+1)%mod;
    }
    scanf("%d",&q);
    while(q--){
        int x,y;
        scanf("%d%d",&x,&y);
        if(a[x]==a[y]) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}
signed main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);cout.tie(0);
    int t;
    scanf("%d",&t);
    while(t--)
    solve();
    return 0;
}

1009 Assertion

m代表了物品的数量,n代表了组数,问不管怎么分配,是否肯定有一组物品数量大于等于d

进行一个平均分配,每组物品基础数量是m/n,然后有m%n大于0的话,那么肯定有一组物品数量等于m/n+1

AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
//#define int long long
//#define endl '\n'
using namespace std;
void solve()
{
    int n,m,d;
    cin>>n>>m>>d;
    int x=m%n;
    if(x>0) x=1;
    if(m/n+x>=d) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    cin.tie(nullptr);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}

1012 Play on tree 

SG函数,先上结论,叶子节点的SG值为0,中间节点的SG值为它的所有子节点的SG值加1后的异或和(不予证明,直接记结论)

先跑一遍dfs1,从顶点1往下搜到叶子节点,然后通过回溯算出所有的点,以它们为顶点往下的整棵子树(包括顶点)的SG值

所以跑第一遍dfs1时,就算出了以1为根的SG值,以及以其它点为顶点往下的整棵子树的SG值,但是我们需要的是以各个节点为根的SG值,所以还需要跑一遍dfs2来进行换根操作,来算出以其它各个节点为根的SG值

f1[i]记录的是以i为顶点往下的SG值,f2[i]记录的是以i为根的SG值

dfs1与dfs2的比较:

dfs1是先搜到叶子节点,然后回溯算出以每个点为顶点的子树的SG值

dfs2是从顶点开始搜,一边搜一边算以该点为根的SG值

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=2e5+10,mod=1e9+7;
int f1[N],f2[N];
int n;
vector<vector<int>>e(N);
inline int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=1ll*ans*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ans;
}
inline void dfs1(int u,int fa){
    int now=0;
    for(auto v:e[u]){
        if(v==fa) continue;
        dfs1(v,u);
        now^=(f1[v]+1);
    }
    f1[u]=now;
    return;
}
inline void dfs2(int u,int fa){
    for(auto v:e[u]){
        if(v==fa) continue;
        f2[v]=f1[v]^((f2[u]^(f1[v]+1))+1);
        dfs2(v,u);
    }
    return;
}
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++) e[i].clear();
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,1);
    f2[1]=f1[1];
    dfs2(1,1);
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(f2[i]) cnt++;
    }
    cout<<1ll*cnt*qpow(n,mod-2)%mod<<'\n';
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值