暑假集训日记——8.17(codeforce)

这篇博客记录了作者在暑假集训期间对Codeforce中三道算法题目的解题思路。第一题C. Watching Fireworks is Fun,通过动态规划和单调队列优化求解;第二题C. Colorful Tree,利用树形DP和容斥原理解决颜色路径总权值问题;第三题D. Recovering BST,探讨如何根据给定条件构造二叉搜索树,涉及区间DP;最后一题D. Zuma,讨论消除回文串的最小操作次数问题。
摘要由CSDN通过智能技术生成

C. Watching Fireworks is Fun
题意:
一条街道有 n n n个区域。 从左到右编号为 1 1 1 n n n。 相邻区域之间的距离为 1 1 1。在节日期间,有 m m m次烟花要燃放。 第 i i i次烟花燃放区域为 a i a_i ai,幸福属性为 b i b_i bi,时间为 t i t_i ti t i ⩽ t i + 1 t_i⩽t_i+1 titi+1
如果你在第 i i i次烟花发射时在 x ( 1 ⩽ x ⩽ n ) x(1⩽x⩽n) x1xn处,你将获得幸福值 b i − ∣ a i − x ∣ bi−|ai−x| biaix(请注意,幸福值可能是负值)。
你可以在单位时间间隔内移动最多 d d d个单位,但禁止走出主要街道。 此外,您可以在初始时刻(时间等于1时)处于任意区域,并希望最大化从观看烟花中获得的幸福总和。

题解:dp+单调队列+滚动数组优化
如果时间重叠,就直接状态转移,否则就单调队列优化一下。
状态转移方程:
d p [ i ] [ j ] = d p [ i − 1 ] [ k ] + b i − ∣ a i − j ∣ dp[i][j]=dp[i-1][k]+b_i-|a_i-j| dp[i][j]=dp[i1][k]+biaij ( ∣ j − k ∣ &lt; = ( t i − t i − 1 ) ∗ d ) (|j-k|&lt;=(t_i-t_{i-1})*d) (jk<=(titi1)d)

#include<bits/stdc++.h>
#define mp make_pair
#define se second
#define fi first
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;

const int N=5e5+10;
const int MAXN=20010;
const int INF=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const double eps=0.0000001;
const ll mod=1e9+7;
ll n,m,x,y,z,k,cnt,t,len;
ll a[N],b[N],ti[N];
ll dp[2][150005];///滚动数组优化

struct node
{
    ll x,y;
}v[150005];


int main()
{
   ll d;
   scanf("%lld%lld%lld",&n,&m,&d);
   for(int i=1;i<=m;i++)
   {
       scanf("%lld%lld%lld",&a[i],&b[i],&ti[i]);
   }
    ti[0]=ti[1];
    int id=1;
    for(int i=1;i<=m;i++,id=1-id)
    {
        if(ti[i]==ti[i-1])
        {
            for(int j=1;j<=n;j++)
            {
                dp[id][j]=dp[1-id][j]+b[i]-abs(a[i]-j);
            }
            continue;
        }
        int head=1,tail=0,k=1;
        for(int j=1;j<=n;j++)
        {
            while(k<=n&&k<=(ti[i]-ti[i-1])*d+j)
            ///由于区间大小不固定,需要枚举一下右端点
            {
                while(head<=tail && v[tail].y<dp[1-id][k]) tail--;
                v[++tail].y=dp[1-id][k],v[tail].x=k;
                k++;
            }

            while(head<=tail&&j-v[head].x>(ti[i]-ti[i-1])*d) head++;
            if(head<=tail)
            dp[id][j]=v[head].y+b[i]-abs(a[i]-j);
        }
    }
    ll ans=-INF64;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,dp[1-id][i]);
    }
    printf("%I64d\n",ans);
}

C. Colorful Tree
题意:
给你一个包含 n n n个节点的树,用一个数字代表一种颜色,树上的路径权值为在这条路径上包含的颜 色数量,这棵树总共有 n ∗ ( n − 1 ) / 2 n*(n-1)/2 n(n1)/2条路径。求这棵树上所有路径的总权值。

题解:树形dp+容斥
如果正向想这道题就很麻烦,不好下手(反正我是不会做)。然后看了网上的博客和官方题解,采用的是逆向思维:先认为每个节点有所有的颜色,所以贡献就是 c ∗ n ∗ ( n − 1 ) / 2 c*n*(n-1)/2 cn(n1)/2 , c c c是颜色总数。然后减去每个颜色没有经过的路径,这样有一个好处,就是把不符合的路径分为一个一个的联通块,因此不符合的路径就是 c o u n t ∗ ( c o u n t − 1 ) / 2 count*(count-1)/2 count(count1)/2 c o u n t count count表示不同颜色的节点数。其实仔细想想就是容斥的思想。
需要注意的就是不经过的路径分为两种来计算
1.与这个颜色的子叶节点中不同颜色的路径
2.与这个颜色的父亲节点中不同颜色的路径
参考博客

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+100;
typedef long long ll;
int c[N],vis[N];
vector<int> e[N];
ll sum[N];///sum[i]表示的是子叶节点的颜色为 i的子树大小
ll sizes[N];///子树大小

ll ans;

void dfs(int x,int y)
{
    sizes[x]=1;
    sum[c[x]]++;
    ll pre=sum[c[x]];///pre表示计算完的节点个数
    for(int i=0; i<e[x].size(); i++)
    {
        if(e[x][i]==y) continue;
        dfs(e[x][i],x);
        sizes[x]+=sizes[e[x][i]];
        ll counts=sizes[e[x][i]]-(sum[c[x]]-pre);
        ///counts表示的是根节点到颜色为c[x] 的子叶节点间不同颜色的点的个数
        ///(sum[c[x]]-pre)为这棵子树的颜色为 c[x]的子叶节点的子树大小
        ans=ans+(1LL*counts*(counts-1))/2;
        sum[c[x]]+=counts;
        pre=sum[c[x]]; 
    }
}

int main()
{
    int n,cas=1;
    while(scanf("%d",&n)!=EOF)
    {
        int num=0;
        ans=0;
       memset(sum,0,sizeof(sum));
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=n; i++)
        {
            e[i].clear();
            scanf("%d",&c[i]);
            if(vis[c[i]]==0)
            {
                vis[c[i]]=1;
                ///每个颜色仅标记一次,目的是为了计算第二种不能走的路径
                num++;
            }
        }
        for(int i=1; i<n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs(1,0);///求第一种不你能走得路径
        ll ANS = 1LL*num*((1LL)*n*(n-1))/2;
        for(int i=1; i<=n; i++) ///求第二种不能走的路径
        {
            if(vis[i])
            {
                ll ct=n-sum[i];
                ans+=ct*(ct-1)/2;
            }
        }
        printf("Case #%d: %lld\n", cas++, ANS-ans);
    }
}

还没过的代码!!!,以后有时间再看看,这个思路就是直接减去子树大小。

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+100;
typedef long long ll;
int c[N],vis[N];
vector<int> e[N];
ll sum[N];///sum[i]表示的是子叶节点的颜色为 i的子树大小
ll sizes[N];///子树大小
ll bone[N];///bone[N]子树的子树大小

ll ans;

void dfs(int x,int y)
{
    sizes[x]=1;
    sum[c[x]]++;
    //ll pre=sum[c[x]];///pre表示计算完的节点个数
    for(int i=0; i<e[x].size(); i++)
    {
        if(e[x][i]==y) continue;
        bone[c[x]]=0;
        dfs(e[x][i],x);
        sizes[x]+=sizes[e[x][i]];
        //ll counts=sizes[e[x][i]]-(sum[c[x]]-pre);
        ll counts=sizes[e[x][i]]-bone[c[x]];
        ///counts表示的是根节点到颜色为c[x] 的子叶节点间不同颜色的点的个数
        ///(sum[c[x]]-pre)为这棵子树的颜色为 c[x]的子叶节点的子树大小
        ans=ans+(1LL*counts*(counts-1))/2;
        sum[c[x]]+=counts;
       // pre=sum[c[x]];
    }
    bone[c[x]]=sizes[x];
}

int main()
{
    int n,cas=1;
    while(scanf("%d",&n)!=EOF)
    {
        int num=0;
        ans=0;
        memset(sum,0,sizeof(sum));
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=n; i++)
        {
            e[i].clear();
            scanf("%d",&c[i]);
            if(vis[c[i]]==0)
            {
                vis[c[i]]=1;
                ///每个颜色仅标记一次,目的是为了计算第二种不能走的路径
                num++;
            }
        }
        for(int i=1; i<n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs(1,0);///求第一种不你能走得路径
        ll ANS = 1LL*num*((1LL)*n*(n-1))/2;
        for(int i=1; i<=n; i++) ///求第二种不能走的路径
        {
            if(vis[i])
            {
                ll ct=n-sum[i];
                ans+=ct*(ct-1)/2;
            }
        }
        printf("Case #%d: %lld\n", cas++, ANS-ans);
    }
}

D. Recovering BST
题意:
给了n个点的权值。如果gcd大于1就可以相连,否则不能。问能不能构造一个二叉搜索树(左儿子都小于根节点,右儿子都大于根节点)。
题解:区间 d p dp dp
d p [ l ] [ r ] [ k ] dp[l][r][k] dp[l][r][k]代表 [ l , r ] [l,r] [l,r]区间作为 k k k儿子是否可行。
参考博客

d f s dfs dfs版本

#include<bits/stdc++.h>
using namespace std;
const int maxn=800;
int G[maxn][maxn],vis[maxn][maxn][4],dp[maxn][maxn][4];
int a[maxn];
int dfs(int rt,int l,int r,int k){
    int flag=0;
    if(vis[l][r][k])
        return dp[l][r][k];
    if(l>r)///如果是等于的话还不能退出,还要看当前节点和根节点是否有边。
        return 1;
    for(int i=l;i<=r;i++){///在l——r区间里以i为根节点
        if(G[i][rt] &&dfs(i,l,i-1,0) && dfs(i,i+1,r,1)){
            flag=1;
            break;
        }
    }
    vis[l][r][k]=1;///无论根是谁,这块区间我搜过了就是搜过了
    dp[l][r][k]=flag;
    return flag;
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
    memset(G,0,sizeof(G));
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j)
                continue;
            if(__gcd(a[i],a[j])!=1){
                G[i][j]=1;
            }
        }
    }
   /// cout<<"&&:"<<G[1][2]<<endl;
    int xpp=0;
    for(int i=1;i<=n;i++){
    ///    cout<<"i:"<<i<<"   "<<"&&&&:"<<dfs(i,1,i-1,0)<<"   "<<dfs(i,i+1,n,1)<<endl;
        if(dfs(i,1,i-1,0)&&dfs(i,i+1,n,1))
        {
            printf("Yes\n");
            xpp=1;
            break;
        }
    }
    if(!xpp){
        printf("No\n");
    }
    }
    return 0;
 
}

递推版本:

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

#define maxn 700
#define rep(i,x,y) for(int i=x;i<=y;i++)

int n;
int a[maxn+5];
int g[maxn+5][maxn+5];
bool L[maxn+5][maxn+5],R[maxn+5][maxn+5];

int GCD(int x,int y) {
	if(y==0) return x;
	return GCD(y,x%y);
}

int main() {
	scanf("%d",&n);
	rep(i,1,n) {
		scanf("%d",&a[i]);
	}
	rep(i,1,n) {
		rep(j,1,n) {
			g[i][j]=GCD(a[i],a[j]);
			if(g[i][j]==1) g[i][j]=0;
			else g[i][j]=1;
		}
	}
	rep(i,1,n) L[i][i]=R[i][i]=1;
	rep(j,1,n-1) rep(i,1,n-j) {
		int k=i+j;
		rep(p,i+1,k) L[i][k]|=(g[i][p]&L[p][k]&R[i+1][p]);
		rep(p,i,k-1) R[i][k]|=(g[p][k]&L[p][k-1]&R[i][p]);
	}
	rep(i,1,n) {
		if(L[i][n]&&R[1][i]) {
			printf("Yes");
			return 0;
		}
	}
	printf("No");
	return 0;
}

D. Zuma
题意:
给你一个长度为 n n n的序列,每一次可以消去一段回文串,问你最少消去多少次,能够消除整个序列。
题解:别人的题解

#include<bits/stdc++.h>
#define LL long long
#define fi first
#define se second
#define mk make_pair
#define pii pair<int,int>
#define ull unsigned long long
using namespace std;

const int N=500+7;
const int M=100+7;
const int inf=0x3f3f3f3f;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9 + 9;

int n, a[N], f[N][N];

int dp(int i, int j) {
    	if(i >= j) return 1;

     	if(i + 1 == j) {
        	if(a[i] == a[j]) return 1;
        	else return 2;
    	}
    	if(f[i][j] != -1) return f[i][j];
    	f[i][j] = dp(i + 1, j) + 1;
    	f[i][j] = min(f[i][j],dp(i, j-1) + 1);
    	if(a[i] == a[j]) {
       f[i][j] = min(f[i][j], dp(i + 1, j - 1));
   		}
    for(int k = i + 1; k < j; k++) {
         if(a[k] == a[i]) {
            f[i][j] = min(f[i][j], dp(i + 1, k - 1) + dp(k + 1, j));
         }
     }
     return f[i][j];
 }
 int main() {
     memset(f, -1, sizeof(f));
     scanf("%d", &n);
    	for(int i = 1; i <= n; i++) {
         scanf("%d", &a[i]);
     }
    int ans = dp(1, n);
    printf("%d\n", ans);
    return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值