USACO 2019 January Contest, Platinum 题解

Problem 1. Redistricting

\(H\)看做\(+1\)\(G\)看做\(-1\),得到一个前缀和数组\(sum_i\)

\(dp_i\)为考虑到\(i\)时的答案,有\(dp_i=min(dp_j+[sum_i-sum_j\leq 0])\)

直接\(dp\)时间是\(O(nk)\)的,使用单调队列优化时间复杂度降为\(O(nlog_2k)\)

注意单调队列是双关键字的,第一关键字是\(dp\),第二是\(sum\)

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define fir first
#define sec second
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define maxd 1000000007
#define eps 1e-6
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
int n,k,dp[300300],sum[300500];
char s[300500];
struct hnode{int val,id;};
bool operator <(hnode p,hnode q)
{
    if (p.val==q.val) return sum[p.id]>sum[q.id];
    else return p.val>q.val;
}
priority_queue<hnode> q;

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

int main()
{
    freopen("redistricting.in","r",stdin);
    freopen("redistricting.out","w",stdout);
    n=read();k=read();
    scanf("%s",s+1);
    rep(i,1,n) 
        if (s[i]=='H') sum[i]=sum[i-1]+1;else sum[i]=sum[i-1]-1;
    memset(dp,0x3f,sizeof(dp));dp[0]=0;
    q.push((hnode){0,0});
    rep(i,1,n)
    {
        while (q.top().id<i-k) q.pop();
        dp[i]=q.top().val;
        if (sum[q.top().id]>=sum[i]) dp[i]++;
        q.push((hnode){dp[i],i});
    }
    printf("%lld",dp[n]);
    return 0;
}

Problem 2. Exercise Route

对于一条非树边\((u_1,v_1)\),将它加入一棵树后树上刚好出现一个环

要使得加入两条非树边\((u_1,v_1),(u_2,v_2)\)后形成一个包含这两条边的环,则原来的两个小环路径必有交

问题转化成有多少对\((u_1,v_1).(u_2,v_2)\)满足在树上的两条路径有交边

先考虑一个一维的问题:给出\(n\)个区间\([L_i,R_i]\),询问有多少对区间有交集

做法:对于每个\(L_x\),答案减去\(L_i<L_x\)的区间数;对于每个\(R_x\),答案加上\(L_I<R_x\)的区间数,这样的话对于一个区间\([L_x,R_x]\),我们统计了所有的\(L_x\leq L_i\leq R_x\)的区间,符合区间交的定义

实现:我们类比差分的思想,在\(L_i\)处打上\(+1\)\(tag\),之后做一遍前缀和。注意预处理掉\(L_i=L_j\)的情况

在树上的话,我们将路径\((u,v)\)拆成\((u,lca)\)\((v,lca)\),这样做一遍树上差分+树上前缀和即可

具体的,对于一条路径\((u,lca)\),我们找到\(lca\)在该路径上的儿子\(p\),在\(p\)上打一个\(+1tag\)即可

注意避免重复计数

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define fir first
#define sec second
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define maxd 1000000007
#define eps 1e-6
#define mp make_pair
typedef long long ll;
typedef pair<int,int> pii;
const int N=100000;
const double pi=acos(-1.0);
struct node{int to,nxt;}sq[400200];
int all=0,head[200200];
int n,m,sp[200200][2],fa[200200][20],dep[200200],lca[200200];
ll tag[200200],val[200200];
map<pii,int> tax;

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void add(int u,int v)
{
    all++;sq[all].to=v;sq[all].nxt=head[u];head[u]=all;
}

void dfs1(int u,int fu)
{
    dep[u]=dep[fu]+1;fa[u][0]=fu;
    rep(i,1,19)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    int i;
    for (i=head[u];i;i=sq[i].nxt)
    {
        int v=sq[i].to;
        if (v!=fu) dfs1(v,u);
    }
}

int LCA(int u,int v)
{
    if (dep[u]<dep[v]) swap(u,v);
    int tmp=dep[u]-dep[v];
    per(i,19,0)
        if ((tmp>>i)&1) u=fa[u][i];
    if (u==v) return u;
    per(i,19,0)
        if (fa[u][i]!=fa[v][i]) {u=fa[u][i];v=fa[v][i];}
    return fa[u][0];
}

int nxt(int u,int fu)
{
    if (u==fu) return -1;
    per(i,19,0)
        if ((fa[u][i]) && (dep[fa[u][i]]>dep[fu])) u=fa[u][i];
    return u;
}

void dfs2(int u,int fu)
{
    val[u]=val[fu]+tag[u];int i;
    for (i=head[u];i;i=sq[i].nxt)
    {
        int v=sq[i].to;
        if (v!=fu) dfs2(v,u);
    }
}

int main()
{
    freopen("exercise.in","r",stdin);
    freopen("exercise.out","w",stdout);
    n=read();m=read();
    rep(i,1,n-1)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    dfs1(1,0);ll ans=0;
    rep(i,n,m)
    {
        sp[i][0]=read();sp[i][1]=read();
        lca[i]=LCA(sp[i][0],sp[i][1]);
        int nx=nxt(sp[i][0],lca[i]),ny=nxt(sp[i][1],lca[i]);
        if (nx!=-1) {tag[nx]++;ans-=tag[nx];}
        if (ny!=-1) {tag[ny]++;ans-=tag[ny];}
        if ((nx!=-1) && (ny!=-1))
        {
            if (nx>ny) swap(nx,ny);
            ans-=tax[mp(nx,ny)];tax[mp(nx,ny)]++;
        }//防止(u_1,v_1)(u_2,v_2)被记做2组
    }
    dfs2(1,0);
    rep(i,n,m)
    {
        int u=sp[i][0],v=sp[i][1];
        ans+=(val[u]+val[v]-val[lca[i]]*2);
    }
    printf("%lld\n",ans);
    return 0;
}
        

Problem 3. Train Tracking 2

具有一定思维难度的\(dp\)

记所有数为\(a_1,a_2,\cdots,a_n\)

注意到当\(c_i\neq c_{i+1}\)时,我们可以确定某些数,这启发着我么去考虑一段\(c\)值相同的连续段

假设我们有一个极大连续段\(c_i=c_{i+1}=\cdots=c_j=v\),那么它们能影响到的数一共有\(j-i+k\)

\(f_i\)表示强制\(a_i=v\)时的方案数,记\(p=10^9-v\),暴力\(dp\)的话就是枚举上一个为\(v\)的位置,即\(f_i=\sum_{j=i-k}^{i-1}p^{i-j-1}f_j\),显然超时

优化的话考虑将上面的那个式子进行错位相减,得到\(f_i=pf_{i-1}-p^kf_{i-k-1}\)。这个式子的实际意义时,第\(i-1\)个数可以任意取(不必限制为\(v\))的方案数,减去\([i-1,i-k]\)中的数均大于\(v\)的不合法的方案数,时间复杂度\(O(n)\),可以接受

接下来就是防止\(a_i\)被重复考虑或者未被考虑了,若\(c_{i-1}>c_i\)可以看做是\(a_{i+k-1}\)已被确定,同时它以前的数已在\(c_{i-1}\)中被考虑到(因为\(a_{i+k-2}...a_i\geq c_{i-1}>c_i\)),此时计算的\(a\)的长度就会减少一个\(k\),对于\(c_{j+1}>c_j\)的情况也是如此

最后就是长度为\(len\)的序列的答案应为\(f_{len+1}\),因为我们并未强制\(a\)的最后一位是\(v\)

代码十分简单

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define fir first
#define sec second
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define maxd 1000000007
#define eps 1e-6
typedef long long ll;
const int N=1000000000;
const double pi=acos(-1.0);
int n,k,a[100100];
ll dp[100100];

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

ll qpow(ll x,int y)
{
    ll ans=1;
    while (y)
    {
        if (y&1) ans=ans*x%maxd;
        x=x*x%maxd;y>>=1;
    }
    return ans;
}

ll solve(int val,int len)
{
    dp[0]=1;dp[1]=1;
    int i;ll p=N-val,tmp=qpow(p,k);
    rep(i,2,len+1)
    {
        dp[i]=(p+1)*dp[i-1]%maxd;
        if (i-k-1>=0) dp[i]=(dp[i]-tmp*dp[i-k-1]%maxd+maxd)%maxd;
    }
    return dp[len+1];
}

int main()
{
    freopen("tracking2.in","r",stdin);
    freopen("tracking2.out","w",stdout); 
    n=read();k=read();
    rep(i,1,n-k+1) a[i]=read();
    int ans=1,i,j=0;
    for (i=1;i<=n-k+1;i=j+1)
    {
        j=i;
        while ((j<=n-k+1) && (a[i]==a[j])) j++;j--;
        int len=j+k-i;
        if ((i>1) && (a[i]<a[i-1])) len-=k;
        if ((j<n-k+1) && (a[j]<a[j+1])) len-=k;
        if (len>0) ans=ans*solve(a[i],len)%maxd;
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/encodetalker/p/11234249.html
今日推荐