「JOI 2017/2018 决赛」题解

LOJ 2347~2351
BZOJ上只有其中2道: 4273,4279

寒冬暖炉
dp可以推个柿子把转移优化到 O ( 1 ) ,再套个wqs二分把状态数优化到 O ( n l o g n )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define ld long double
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;
const ld eps = 1e-12;

int n,K;
int a[maxn],b[maxn],ti[maxn];

struct node
{
    int k; ld x;
}f[maxn];
int dp(ld mid)
{
    f[0]=(node){0,0.0};
    int nown=0;
    for(int i=1;i<=n;i++)
    {
        f[i].k=f[nown].k+1;
        f[i].x=f[nown].x-b[nown]+(ld)a[i]+mid;
        if(f[i].x-b[i]<f[nown].x-b[nown]) nown=i;
    }
    return f[n].k;
}

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    read(n); read(K);
    for(int i=1;i<=n;i++) read(ti[i]);
    for(int i=0;i<=n;i++) a[i]=ti[i]+1,b[i]=ti[i+1];

    ld l=-ti[n],r=ti[n];
    while(r-l>eps)
    {
        ld mid=(l+r)/2.0;
        int k=dp(mid);
        if(k==K) { l=mid;break; }
        if(k<K) r=mid;
        else l=mid;
    }
    printf("%.0Lf\n",f[n].x-l*K);

    return 0;
}

美术展览
从小到大枚举Amax,最优的Amin可以 O ( 1 ) 维护

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

void read(ll &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10ll)+=c-'0';
}
const int maxn = 510000;

int n;
struct node
{
    ll a,b;
    friend inline bool operator <(const node x,const node y){return x.a<y.a;}
}a[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    scanf("%d",&n);
    for(int i=1;i<=n;i++) read(a[i].a),read(a[i].b);
    sort(a+1,a+n+1);

    ll sum=0,pn=a[1].a,ans=0;
    for(int i=1;i<=n;i++)
    {
        sum+=a[i].b;
        ans=max(ans,pn-a[i].a+sum);
        pn=max(pn,a[i+1].a-sum);
    }
    printf("%lld\n",ans);

    return 0;
}

团子制作

将RGB定义为012,将一串团子在1处计数
发现不在同一对角线的1,他们串的团子不可能相交,同一对角线只有相邻的1有可能相交,对每一个对角线做一个dp,复杂度是线性的

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void up(int &a,const int &b){if(a<b)a=b;}
const int maxn =  3100;

int n,m,ans;
int a[maxn][maxn];
int Matchl(int i,int j)
{
    return a[i][j-1]==0&&a[i][j]==1&&a[i][j+1]==2;
}
int Matchc(int i,int j)
{
    return a[i-1][j]==0&&a[i][j]==1&&a[i+1][j]==2;
}
int f[2][3];

char str[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    memset(a,-1,sizeof a);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",str+1);
        for(int j=1;j<=m;j++) a[i][j]=str[j]=='R'?0:(str[j]=='G'?1:2);
    }

    int ans=0;
    for(int s=2;s<=n+m;s++)
    {
        memset(f,0,sizeof f); int now=0;
        for(int j=max(1,s-n);j<=m&&j+1<=s;j++)
        {
            int i=s-j;
            now=!now;
            for(int l=0;l<3;l++)
            {
                int &temp=f[!now][l];
                up(f[now][0],temp);
                if(l!=2&&Matchl(i,j)) up(f[now][1],temp+1);
                if(l!=1&&Matchc(i,j)) up(f[now][2],temp+1);
                temp=0;
            }
        }
        up(f[now][0],f[now][1]);
        up(f[now][0],f[now][2]);
        ans+=f[now][0];
    }
    printf("%d\n",ans);

    return 0;
}

月票购买

一开始不会做,想了想部分分发现好像会了qaq
不难证明最终答案里U到V的最短路径,和S到T的月票线路只会有一段相交
枚举相交那一段的方向,也就是S->T和T->S两个方向,给每条有可能在ST最短路上的边定向,规定如果这条边要走0费用,一定要沿这个方向走
然后每个点拆3个点:还没相交,在相交段内,已过了相交段,跑Dijskral

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;
const int maxm = 410000;

ll ans;
int n,m,S,T,U,V;
int e[maxm][3],ec[maxm];
struct Graph
{
    struct edge{int y,c,i,nex;}a[maxm]; int len,fir[maxn];
    inline void ins(const int x,const int y,const int c,const int i)
    {
        a[++len]=(edge){y,c,i,fir[x]};fir[x]=len;
    }

    struct node
    {
        ll x; int i;
        friend inline bool operator <(const node &x,const node &y){return x.x>y.x;}
    }; priority_queue<node>q;

    ll dis1[maxn],dis2[maxn],d[maxn<<1];
    void Dij(int st,ll dis[])
    {
        for(int i=1;i<=n;i++) dis[i]=LLONG_MAX;
        dis[st]=0; q.push((node){0ll,st});
        while(!q.empty())
        {
            const node now=q.top(); q.pop();
            int x=now.i; if(dis[x]!=now.x) continue;
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(dis[y]>dis[x]+a[k].c)
                dis[y]=dis[x]+a[k].c,q.push((node){dis[y],y});
        }
    }
    void solve(int st,ll D,ll d1[],ll d2[])
    {
        for(int i=1;i<=m;i++) ec[i]=-1;
        for(int x=1;x<=n;x++)
        {
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(d1[x]<d1[y]&&d1[x]+a[k].c+d2[y]==D)
                ec[a[k].i]=x<y;
        }

        for(int i=1;i<=n*3;i++) d[i]=LLONG_MAX;
        d[U]=0; q.push((node){d[U],U});
        while(!q.empty())
        {
            const node now=q.top(); q.pop();
            int x=now.i; if(d[x]!=now.x) continue;
            int use=(x-1)/n; x=(x-1)%n+1;

            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
            {
                if(use!=2&&ec[a[k].i]==(x<y))
                {
                    if(d[y+n]>now.x) d[y+n]=now.x,q.push((node){d[y+n],y+n});
                }
                int nu=use==1?2:use;
                if(d[y+nu*n]>now.x+a[k].c)
                    d[y+nu*n]=now.x+a[k].c,q.push((node){d[y+nu*n],y+nu*n});
            }
        }
        for(int i=0;i<3;i++) ans=min(ans,d[i*n+V]);
    }
}g;
void Solve()
{
    ans=LLONG_MAX;
    g.solve(S,g.dis1[T],g.dis1,g.dis2);
    g.solve(T,g.dis2[S],g.dis2,g.dis1);
}

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    read(n); read(m);
    read(S); read(T);
    read(U); read(V);
    for(int i=1;i<=m;i++)
    {
        int x,y,c; read(x),read(y),read(c);
        g.ins(x,y,c,i); g.ins(y,x,c,i);
        e[i][0]=x,e[i][1]=y,e[i][2]=c;
    }
    g.Dij(S,g.dis1); g.Dij(T,g.dis2);

    Solve();
    printf("%lld\n",ans);

    return 0;
}

毒蛇越狱

关于通配符的处理,一种方法是用子集和处理,但用子集和有个问题,就是询问串中要求为1的位置可能会算到0的情况,我们可以套一个容斥,但这个容斥的复杂度是单次 O ( 2 L ) 的,太高了无法接受
观察发现1的个数,0的个数,?的个数最小值<=6,我们可以分三类,1的个数<=6时做子集和的容斥,0的个数<=6的时候,类似1的,我们可以另外做一个反向的子集和然后容斥,?的个数<=6时我们可以直接枚举所有匹配的情况统计和

总复杂度 O ( 2 L + 2 6 q )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define cal(x) __builtin_popcount(x)
using namespace std;

const int maxn = (1<<20)+20;

int n,m,al;
int s[maxn],f1[maxn],f0[maxn];

char str[maxn];

int main()
{
    //freopen("tmp.in","r",stdin);
    //freopen("tmp.out","w",stdout);

    scanf("%d%d",&n,&m); al=1<<n;
    scanf("%s",str);
    for(int i=0;i<al;i++) f1[i]=f0[i]=s[i]=str[i]-'0';

    for(int i=0;i<n;i++)
        for(int t=1<<i,j=0;j<al;j++) if(!(j>>i&1))
        {
            f1[j+t]+=f1[j];
            f0[j]+=f0[j+t];
        }

    while(m--)
    {
        scanf("%s",str);
        int x=0,y=0,z=0;
        for(int i=0;i<n;i++) str[n-i-1]=='1'?x|=1<<i:(str[n-i-1]=='0'?y|=1<<i:z|=1<<i);

        int ans=0;
        if(cal(x)<=6)
        {
            for(int i=x;;i=(i-1)&x)
            {
                (cal(x^i)&1)?ans-=f1[i|z]:ans+=f1[i|z];
                if(!i) break;
            }
        }
        else if(cal(y)<=6)
        {
            for(int i=y;;i=(i-1)&y)
            {
                (cal(i)&1)?ans-=f0[i|x]:ans+=f0[i|x];
                if(!i) break;
            }
        }
        else
        {
            for(int i=z;;i=(i-1)&z)
            {
                ans+=s[i|x];
                if(!i) break;
            }
        }
        printf("%d\n",ans);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/l_0_forever_lf/article/details/80494422