[USACO 2018 Feb Gold] Tutorial

Link:

USACO 2018 Feb Gold 传送门

A:

$dp[i][j][k]$表示前$i$个中有$j$个0且末位为$k$的最优解

状态数$O(n^3)$

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=105,INF=1<<30;
int n,dat[MAXN],dp[MAXN][MAXN][MAXN];

int main()
{    
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
        scanf("%d",&dat[i]);
    for(int i=0;i<MAXN;i++)
        for(int j=0;j<MAXN;j++)
            for(int k=0;k<MAXN;k++)
                dp[i][j][k]=INF;    
    
    if(dat[1]!=0) dp[1][1][0]=1;
    else dp[1][1][0]=0;
    for(int i=1;i<n;i++)
        for(int j=1;j<=i;j++)
            for(int k=0;k<=i-j;k++)
                if(dp[i][j][k]!=INF)
                {
                    dp[i+1][j][k+1]=min(dp[i+1][j][k+1],dp[i][j][k]+(dat[i+1]!=k+1));
                    dp[i+1][j+1][0]=min(dp[i+1][j+1][0],dp[i][j][k]+(dat[i+1]!=0));
                }
    for(int i=1;i<=n;i++)
    {
        int res=INF;
        for(int j=0;j<=n;j++)
            res=min(res,dp[n][i][j]);
        printf("%d\n",res);
    }
    return 0;
}
Problem A

 

B:

对于每一个节点分别计算其上方和下方的答案,其中下方答案明显可以一遍$dfs$

上方答案我一开始是$O(树高)$求的,明显会被卡……

其实可以再做一遍$dfs$,用父节点除去该棵子树的贡献再加上走到父节点的贡献即可

注意特殊处理叶子结点

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+10;
char s[MAXN][20];
struct edge{int nxt,to;}e[MAXN<<2];
int n,x,head[MAXN],len[MAXN],f[MAXN],num[MAXN],tot;
ll sum[MAXN],cnt[MAXN],tmp,res[MAXN],mn=1ll<<60,lf;

void add_edge(int x,int y)
{
    e[++tot]={head[x],y};head[x]=tot;
    e[++tot]={head[y],x};head[y]=tot;
}

void dfs1(int x,int anc)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(e[i].to==anc) continue;
        f[e[i].to]=x;dfs1(e[i].to,x);
        cnt[x]+=cnt[e[i].to];
        sum[x]+=sum[e[i].to]+cnt[e[i].to]*(len[e[i].to]+1);        
    }
}

void dfs2(int x,int anc)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(e[i].to==anc||!num[e[i].to]) continue;
        res[e[i].to]=sum[e[i].to];
        res[e[i].to]+=(res[x]-sum[e[i].to]-cnt[e[i].to]*(len[e[i].to]+1));
        res[e[i].to]+=3*(lf-cnt[e[i].to]);mn=min(mn,res[e[i].to]);
        dfs2(e[i].to,x);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s%d",s[i]+1,&num[i]);
        len[i]=strlen(s[i]+1);
        for(int j=1;j<=num[i];j++)
            scanf("%d",&x),add_edge(x,i);
        if(!num[i]) cnt[i]=1,len[i]--,lf++;
    }
    dfs1(1,0);
    res[1]=mn=sum[1];
    dfs2(1,0);
    
    printf("%lld",mn);
    return 0;
}
Problem B

 

C:

好像和前面一题差不多?

可以离线从小到大加点用$set$和$multiset$维护当前点集和答案

不过官网上的标程常数更小:反向从大到小加点,用链表维护当前最大空隙

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+10;
multiset<int,greater<int> > mx;
set<int> s;set<int>::iterator it;
int n,m,res[MAXN];P sw[MAXN];
struct Query{int s,d,id;}bt[MAXN];
bool cmp(Query a,Query b){return a.s<b.s;}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) 
        scanf("%d",&sw[i].X),sw[i].Y=i;
    for(int i=1;i<=m;i++)
        scanf("%d%d",&bt[i].s,&bt[i].d),bt[i].id=i;
    sort(sw+1,sw+n+1);sort(bt+1,bt+m+1,cmp);
    
    int lst=1,pre,nxt;
    s.insert(1);s.insert(n);mx.insert(n-1);
    for(int i=1;i<=n;i++)
    {
        while(sw[lst].X<=bt[i].s&&lst<=n)
        {
            if(sw[lst].Y!=1&&sw[lst].Y!=n)
            {
                it=s.lower_bound(sw[lst].Y);
                nxt=*it;pre=*(--it);
                mx.erase(mx.find(nxt-pre));
                mx.insert(nxt-sw[lst].Y);mx.insert(sw[lst].Y-pre);    
            }
            s.insert(sw[lst].Y);lst++;
        }
        res[bt[i].id]=(*mx.begin()<=bt[i].d);
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",res[i]);
    return 0;
}
Problem C

如果将问题转化为在原序列中大量删除的,手写链表即可

 

转载于:https://www.cnblogs.com/newera/p/9602877.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值