2017 ACM-ICPC World Finals 题解

时间:2022-04-22 18:20:31

先贴官方题解:http://www.csc.kth.se/~austrin/icpc/finals2017solutions.pdf

Problem L Visual Python++
bzoj4959

不难发现匹配是唯一的,用set把匹配处理出来之后,横纵坐标各做一次扫描线判有没有相交或者覆盖但是边界相交的情况
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 lowbit(x) x&(-x)
using namespace std;

const int maxn = 210000;

int n;
int nx0[maxn],ny0[maxn],nx1[maxn],ny1[maxn],cntx,cnty;
struct point
{
    int x,y,i,sig;
}p[maxn];
inline bool cmpx(const point x,const point y){return x.x<y.x;}
inline bool cmpy(const point x,const point y){return x.y==y.y?x.x<y.x:x.y<y.y;}

void Trans()
{
    sort(p+1,p+2*n+1,cmpx); cntx=0;
    for(int i=1,lax=-1;i<=2*n;i++)
    {
        if(lax!=p[i].x) cntx++;
        lax=p[i].x,p[i].x=cntx;
    }
    sort(p+1,p+2*n+1,cmpy); cnty=0;
    for(int i=1,lay=-1;i<=2*n;i++)
    {
        if(lay!=p[i].y) ++cnty;
        lay=p[i].y,p[i].y=cnty;
    }

    for(int i=1;i<=2*n;i++)
    {
        int j=p[i].i;
        if(p[i].sig==1) nx0[j]=p[i].x,ny0[j]=p[i].y;
        else nx1[j]=p[i].x,ny1[j]=p[i].y;
    }
}
int mat[maxn];
struct node
{
    int x,i;
    friend inline bool operator <(const node x,const node y){return x.x<y.x;}
};
set<node>S;
set<node>::iterator it;

struct Segment
{
    int l,r,y,sig;
}a[maxn];
inline bool cmp(Segment x,Segment y){return x.y==y.y?x.sig<y.sig:x.y<y.y;}
struct Trb
{
    int s[maxn],u;
    void add(int x,int c){for(;x<=u;x+=lowbit(x)) s[x]+=c;}
    int q(int x){int re=0;for(;x;x-=lowbit(x))re+=s[x];return re;}
}tr;

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

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        p[i]=(point){x,y,i,1};
    }
    for(int i=n+1;i<=2*n;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        p[i]=(point){x,y,i-n,-1};
    }
    Trans();

    for(int i=1;i<=2*n;i++)
    {
        node tmp=(node){p[i].x,p[i].i};
        if(p[i].sig==1) S.insert(tmp);
        else
        {
            it=S.upper_bound(tmp);
            if(it==S.begin()) return puts("syntax error"),0;
            it--;
            mat[(*it).i]=p[i].i;
            S.erase(it);
        }
    }

    for(int i=1;i<=n;i++)
    {
        int x=i,y=mat[i];
        a[2*i-1]=(Segment){nx0[x],nx1[y],ny0[x],1};
        a[2*i]=(Segment){nx0[x],nx1[y],ny1[y]+1,-1};
    }
    sort(a+1,a+2*n+1,cmp); tr.u=cntx;
    for(int i=1;i<=2*n;i++)
    {
        if(a[i].sig==-1) tr.add(a[i].l,-1),tr.add(a[i].r,-1);
        else
        {
            int tmp=tr.q(a[i].r)-tr.q(a[i].l-1);
            if(tmp) return puts("syntax error"),0;
            tr.add(a[i].l,1); tr.add(a[i].r,1);
        }
    }

    for(int i=1;i<=n;i++)
    {
        int x=i,y=mat[i];
        a[2*i-1]=(Segment){ny0[x],ny1[y],nx0[x],1};
        a[2*i]=(Segment){ny0[x],ny1[y],nx1[y]+1,-1};
    }
    sort(a+1,a+2*n+1,cmp); tr.u=cnty;
    for(int i=1;i<=2*n;i++)
    {
        if(a[i].sig==-1) tr.add(a[i].l,-1),tr.add(a[i].r,-1);
        else
        {
            int tmp=tr.q(a[i].r)-tr.q(a[i].l-1);
            if(tmp) return puts("syntax error"),0;
            tr.add(a[i].l,1); tr.add(a[i].r,1);
        }
    }

    for(int i=1;i<=n;i++) printf("%d\n",mat[i]);

    return 0;
}

Problem K Tarot Sham Boast
bzoj4958
神仙题
我看了很久还是不能理解题解在讲啥….
于是照着题解给的结论写了一遍
结论就是找出每个串的长度 >= 2 l 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
using namespace std;

const int maxn = 100005;
const int maxm = 12;

int n,m,len;
vector<int>V[maxm];
string s[maxm];
char str[maxn];
int nex[maxn];

int a[maxn];
inline int cmpv(const int x,const int y)
{
    for(int i=0;i<V[x].size()&&i<V[y].size();i++) if(V[x][i]!=V[y][i])
        return V[x][i]<V[y][i]?-1:1;
    if(V[x].size()==V[y].size()) return 0;
    return V[x].size()<V[y].size()?-1:1;
}
inline bool cmp(const int x,const int y)
{
    int k=cmpv(x,y);
    return k==0?x<y:(k<0);
}

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

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",str+1); len=strlen(str+1);
        s[i].push_back(str[1]);
        for(int j=2;j<=len;j++)
        {
            nex[j]=nex[j-1];
            while(nex[j]&&str[nex[j]+1]!=str[j]) nex[j]=nex[nex[j]];
            if(str[nex[j]+1]==str[j]) nex[j]++;
            s[i].push_back(str[j]);
        }
        int k=nex[len];
        while(k&&2*len-k<=n)
        {
            V[i].push_back(k);
            k=nex[k];
        }
        a[i]=i;
    }
    sort(a+1,a+m+1,cmp);
    for(int i=1;i<=m;i++) cout<<s[a[i]]<<endl;

    return 0;
}

Problem J Son of Pipe Stream
bzoj4957

首先v是没有用的,我们可以将F的流量*v,最后算答案时除以 v a

我们先分别以1,2为源跑一次最大流,得到Flubber和Water的最大流量 F m a x W m a x ,再新建源连向1,2,跑一次Flubber+Water的最大混合流量 Z ,最优的F就是在 [ Z W m a x , F m a x ] 里最贴近 a Z 的值,于是得到新的 F ,然后 W = Z F ,可以证明这两个流量是一定可以得到且一定是最优的(证明略我也不会证…感受一下?)

然后考虑构造解,新建源连向1,2容量分别为 F , W ,跑一次最大流,然后根据这次跑出来的结果新建图,新的图中每条边的方向就是这次流的方向,容量就是这次的流量,源只连1容量 F 跑一次得到每条边Flubber的流量,只连2容量W’再跑一次得到每条边water的流量

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 inf 1e9
using namespace std;

const int maxn = 210;
const int maxm = 321000;
const double eps = 1e-9;

int n,m; double alpha,Vi;
struct edge{int y,nex; double c;}a[maxm]; int len,fir[maxn],fi[maxn];
inline void ins(const int x,const int y,const double c)
{
    a[++len]=(edge){y,fir[x],c};fir[x]=len;
    a[++len]=(edge){x,fir[y],0};fir[y]=len;
}
int e[maxm][3];
struct Max_Flow
{
    int N,st,ed;
    void Init()
    {
        len=1;
        for(int i=0;i<=N;i++) fir[i]=0;
    }
    int h[maxn];
    queue<int>q;
    bool bfs()
    {
        for(int i=1;i<=N;i++) h[i]=0;
        h[st]=1; q.push(st);
        while(!q.empty())
        {
            const int x=q.front(); q.pop();
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!h[y]&&a[k].c>0)
                h[y]=h[x]+1,q.push(y);
        }
        return h[ed]>0;
    }
    double dfs(const int x,const double flow)
    {
        if(x==ed) return flow;
        double delta=0;
        for(int &k=fi[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
        {
            if(h[y]==h[x]+1&&a[k].c>0)
            {
                double minc=dfs(y,min(a[k].c,flow-delta));
                a[k].c-=minc,a[k^1].c+=minc;
                delta+=minc;
            }
            if(delta==flow) return delta;
        }
        return delta;
    }
    double Flow()
    {
        double ans=0;
        while(bfs())
        {
            for(int i=1;i<=N;i++) fi[i]=fir[i];
            ans+=dfs(st,inf);
        }
        return ans;
    }
}Dicnic;

double F,W,Z,ans;
double val[maxm],ac[maxm],bc[maxm];

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

    scanf("%d%d%lf%lf",&n,&m,&Vi,&alpha);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);

    Dicnic.N=n+1; Dicnic.ed=3;

    Dicnic.st=1; Dicnic.Init();
    for(int i=1;i<=m;i++) 
        ins(e[i][0],e[i][1],e[i][2]),ins(e[i][1],e[i][0],e[i][2]);
    F=Dicnic.Flow();

    Dicnic.st=2; Dicnic.Init();
    for(int i=1;i<=m;i++) 
        ins(e[i][0],e[i][1],e[i][2]),ins(e[i][1],e[i][0],e[i][2]);
    W=Dicnic.Flow();

    Dicnic.st=n+1,Dicnic.Init();
    for(int i=1;i<=m;i++) 
        ins(e[i][0],e[i][1],e[i][2]),ins(e[i][1],e[i][0],e[i][2]);
    ins(n+1,1,inf); ins(n+1,2,inf);
    Z=Dicnic.Flow();
    F=min(F,max(Z-W,alpha*Z));
    W=Z-F;

    Dicnic.Init();
    for(int i=1;i<=m;i++)
        ins(e[i][0],e[i][1],e[i][2]),ins(e[i][1],e[i][0],e[i][2]);
    ins(n+1,1,F); ins(n+1,2,W);
    Dicnic.Flow();
    for(int i=1;i<=m;i++) val[i]=a[(i<<2)-1].c-a[(i<<2)+1].c;

    Dicnic.Init();
    for(int i=1;i<=m;i++)
        val[i]>0?ins(e[i][0],e[i][1],val[i]):ins(e[i][1],e[i][0],-val[i]);
    ins(n+1,1,F);
    Dicnic.Flow();
    for(int i=1;i<=m;i++)
        ac[i]=a[i<<1|1].c*(val[i]>0?1:-1);

    Dicnic.Init();
    for(int i=1;i<=m;i++)
        val[i]>0?ins(e[i][0],e[i][1],val[i]-ac[i]):ins(e[i][1],e[i][0],ac[i]-val[i]);
    ins(n+1,2,W);
    Dicnic.Flow();
    for(int i=1;i<=m;i++)
        bc[i]=a[i<<1|1].c*(val[i]>0?1:-1);

    ans=pow(F,alpha)*pow(W,1.0-alpha)/pow(Vi,alpha);
    for(int i=1;i<=m;i++) printf("%.8lf %.8lf\n",ac[i]/Vi+eps,bc[i]+eps);
    printf("%.10lf\n",ans);

    return 0;
}

Problem I Secret Chamber at Mount Rushmore
bzoj4956
找一下每个字母能转换成哪些字母…

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;

const int maxn = 30;
const int maxm = 1100;

int n,m;
int ind[maxn];
int v[maxn];

char str[110],s1[110],s2[110];

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

    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",str); int x=str[0]-'a';
        scanf("%s",str); int y=str[0]-'a';
        ind[y]|=1<<x;
    }
    for(int i=0;i<26;i++) v[i]=1<<i;
    for(int i=0;i<26;i++)
    {
        int ok=1;
        while(ok)
        {
            ok=0;
            for(int j=0;j<26;j++) if(!(v[i]>>j&1)&&(ind[j]&v[i]))
                ok++,v[i]|=v[j];
        }
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s1); int l1=strlen(s1);
        scanf("%s",s2); int l2=strlen(s2);
        if(l1!=l2) { puts("no");continue; }
        int ok=1;
        for(int j=0;j<l1&&ok;j++) 
            if(!(v[s1[j]-'a']>>s2[j]-'a'&1)) ok=0;
        puts(ok?"yes":"no");
    }

    return 0;
}

Problem H Scenery
bzoj4955
cf上过了bzoj上被卡常……
upd:bzoj上把时限从30s改到60s,35s跑过去了

这是一道论文题qaq,还是比较推荐直接去读题解的原文,毕竟感觉我写的没有解释清楚

首先考虑一种naive的贪心,每次在当前能照的相片中选一个结束时间最早的照
他会在什么情况下错:存在一个相片在这个时候还不能照,但等这张照片照完后这个相片已经过了他的结束时间
把这个错的情况拓展到更一般的情况,就是存在一组相片,我们如果要成功照完他们全部,最迟要在C时刻开始处理这些相片,最早能在s时刻处理这些相片,照一张相片耗时T,那么我们不能在 ( C T , s ) 这个时间段内处理任何一张相片,否则这组相片一定不能在C时刻前得到处理,则一定会有相片拍不成

我们记这些 ( C T , s ) 区间为forbidden区间,就是不能拍摄的时间段,那么只要我们处理出所有这样的时间段,在考虑这些时间段内不拍摄的情况下去执行原来那个naive的贪心,就能保证贪心的结果是最优的

如何处理出这些forbidden区间呢
为方便,记 l 为一个相片最早可以拍摄的时间,记 r 为一个相片最晚可以开始拍摄的时间

最朴素的想法是用 2 n 去枚举这组相片,但显然这个时间复杂度不能接受
注意到很多集合的 l m i n , r m a x 是相同的,令 s = l m i n , t = r m a x ,区间[s,t]内包含的相片越多,他对于forbidden的限制作用越强,显然我们只需要考虑被[s,t]完全包含的所有相片这个集合

于是我们考虑每一个区间[s,t],s是某个左端点,t是某个右端点,对于被[s,t]完全包含的所有照片,我们无视他们的限制(可以证明这样贪是对的,证明略),从后往前贪心放(考虑forbidden的区间下),求出最晚在C时刻拍这个区间内的第一张照片
C < s 则无解
否则当 C < s + t 时, ( C t , s ) 这个区间内就不能拍照(forbidden)

对每个s维护最小的 C t ,按s降序枚举区间,[s,t]的C可以由[s+1,t]的C递推来,若将所有[s,t]都推完仍不出现无解就一定有解(因为[ l m i n , r m a x ]这个区间包含所有相片,他的的C >= l m i n 本身就包含了这n个相片合法这一条件)

复杂度 O ( n 2 )

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 down(int &a,const int &b){if(a>b)a=b;}
const int maxn = 10010;

int n,T;
struct node
{
    int l,r;
    friend inline bool operator <(const node x,const node y){return x.l==y.l?x.r<y.r:x.l<y.l;}
}a[maxn];
int L[maxn],ln,R[maxn],rn;
int pre[maxn],fb[maxn],nowl[maxn];

void NIE(){ puts("no"); exit(0); }

int main()
{
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++)
    {
        int l,r; scanf("%d%d",&l,&r); r-=T;
        L[i]=l,R[i]=r;
        a[i].l=l,a[i].r=r;
    }
    sort(L+1,L+n+1);
    for(int i=1;i<=n;i++) if(i==1||L[i]!=L[i-1]) L[++ln]=L[i];
    sort(R+1,R+n+1);
    for(int i=1;i<=n;i++) if(i==1||R[i]!=R[i-1]) R[++rn]=R[i];

    for(int i=1;i<=ln;i++) fb[i]=L[i];
    for(int i=1;i<=rn;i++) pre[i]=R[i]+T,nowl[i]=ln;
    sort(a+1,a+n+1); int il=ln;
    for(int i=n;i>=1;i--)
    {
        int ni=i-1;for(;ni>=1&&a[ni].l==a[i].l;ni--);ni++;
        int nowj=i;
        for(int j=rn;j>=1&&R[j]>=a[i].l;j--)
        {
            while(nowj>=ni&&a[nowj].r>R[j]) nowj--; 
            if(nowj<ni) break;
            int c=nowj-ni+1;

            int &k=pre[j],&ki=nowl[j];
            while(c)
            {
                c--,k-=T;
                while(k>=a[i].l)
                {
                    while(L[ki-1]>k&&fb[ki]>=k) ki--;
                    if(k<L[ki]&&k>fb[ki]) k=fb[ki];
                    else break;
                }
                if(k<a[i].l) NIE();
            }
            down(fb[il],k-T);
        }
        i=ni; il--;
    }
    puts("yes");

    return 0;
}

Problem G Replicate Replicate Rfplicbte
bzoj4954

注意到无论是否发生bug,每次更新后,黑色区域至少向4个方向拓展一步,所以我们倒着推回去的步数是O(n)步的

假设没有bug,设第k+1步后的网格为Y,我们想倒推出k步后的网格X,我们可以O(nm)逐行递推
假设 ( < i , 1   w ) ( i , < j ) 的所有X已经处理完,有 X [ i ] [ j ] = Y [ i 1 ] [ j 1 ] x o r X [ i 2 ] [ j 2 ] x o r X [ i 2 ] [ j 1 ] x o r X [ i 2 ] [ j ] x o r X [ i 1 ] [ j 2 ] . . . . . . x o r X [ i ] [ j 1 ]
若在(r,c)出现了一个bug,他对r+1行的影响是使得(r+1,c+1),(r+1,c+2),(r+1,c+2k-1),(r+1,c+2k)改变,所以当我们复原到某行r时若X[r][w+1]+X[r][w+2]>0,r-1行一定存在bug,我们再逐列递推找到bug所在的列,把这个bug复原然后继续倒推

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;

const int maxn = 310;

int nx,xx,ny,xy;
int X[maxn][maxn],Y[maxn][maxn];
int ans[maxn][maxn],ansx=-1,ansy;

void init()
{
    //for(int i=ny;i<=xy+2;i++) for(int j=nx;j<=xx+2;j++) X[i][j]=0;
}
int Rec_lin()
{
    for(int i=ny;i<=xy+1;i++)
    {
        for(int j=nx;j<=xx+2;j++) X[i][j]=Y[i-1][j-1]^X[i-2][j-2]^X[i-2][j-1]^X[i-2][j]^
                                        X[i-1][j-2]^X[i-1][j-1]^X[i-1][j]^X[i][j-2]^X[i][j-1];
        if(X[i][xx+1]+X[i][xx+2]>0) return i-1;
    }
    return 0;
}
int Rec_col()
{
    for(int j=nx;j<=xx+1;j++)
    {
        for(int i=ny;i<=xy+2;i++) X[i][j]=Y[i-1][j-1]^X[i-2][j-2]^X[i-2][j-1]^X[i-2][j]^
                                        X[i-1][j-2]^X[i-1][j-1]^X[i-1][j]^X[i][j-2]^X[i][j-1];
        if(X[xy+1][j]+X[xy+2][j]>0) return j-1;
    }
    return 0;
}
void Dec()
{
    for(;ny<xy;ny++)
    {
        int ok=0;
        for(int j=nx;j<=xx;j++) ok+=X[ny][j];
        if(ok) break;
    }
    for(;ny<xy;xy--)
    {
        int ok=0;
        for(int j=nx;j<=xx;j++) ok+=X[xy][j];
        if(ok) break;
    }
    for(;nx<xx;nx++)
    {
        int ok=0;
        for(int i=ny;i<=xy;i++) ok+=X[i][nx];
        if(ok) break;
    }
    for(;nx<xx;xx--)
    {
        int ok=0;
        for(int i=ny;i<=xy;i++) ok+=X[i][xx];
        if(ok) break;
    }
}
void Copy()
{
    for(int i=ny;i<=xy;i++) for(int j=nx;j<=xx;j++) Y[i][j]=X[i][j];
}
void upd()
{
    ansx=xx-nx,ansy=xy-ny;
    for(int i=0;i<=ansy;i++) for(int j=0;j<=ansx;j++)
        ans[i][j]=Y[i+ny][j+nx];
}
void Print()
{
    ny=0,xy=ansy;
    nx=0,xx=ansx;
    for(int i=ny;i<=xy;i++)
    {
        for(int j=nx;j<=xx;j++) putchar(ans[i][j]?'#':'.');
        putchar('\n');
    }
}

char str[maxn];

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

    nx=ny=2; scanf("%d%d",&xx,&xy); xx++;xy++;
    for(int i=ny;i<=xy;i++)
    {
        scanf("%s",str+nx);
        for(int j=nx;j<=xx;j++) Y[i][j]=str[j]=='#';
    }
    for(upd();nx+1<xx&&ny+1<xy;init(),upd())
    {
        int y=Rec_lin();
        if(y)
        {
            int x=Rec_col();
            Y[y][x]^=1;
            y=Rec_lin();
            if(y) break;
        }
        Copy(); Dec();
    }
    Print();

    return 0;
}

Problem F Posterize
bzoj4953

f [ i ] [ j ] [ k ] 表示dp到像素 i ,上一个特殊点取的是 j ,已经用了 k 个特殊点的最优解,转移用前缀和优化一下

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 inf 1e15
using namespace std;

inline void down(ll &a,const ll &b){if(a==-1||a>b)a=b;}
const int maxn = 300;

int n,K;
int pi[maxn];
ll f[2][maxn][maxn],s[maxn][maxn];

ll sqr(int x){ return (ll)x*x; }

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

    scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++) 
    {
        int x; scanf("%d",&x);
        scanf("%d",&pi[x]);
    }

    for(int i=0;i<256;i++)
    {
        s[i][0]=sqr(i)*pi[0];
        for(int j=1;j<256;j++) s[i][j]=s[i][j-1]+sqr(i-j)*pi[j];
    }

    int now=0; memset(f,-1,sizeof f);
    f[now][0][0]=0;
    for(int i=0;i<256;i++)
    {
        now=!now;
        for(int j=0;j<256;j++) for(int k=0;k<=K;k++) if(f[!now][j][k]!=-1)
        {
            ll &temp=f[!now][j][k];
            down(f[now][j][k],temp);
            if(!k) down(f[now][i][k+1],temp+s[i][i]);
            else
            {
                int mid=(i+j)>>1;
                down(f[now][i][k+1],temp+s[j][mid]-s[j][j]+s[i][i]-s[i][mid]);
            }
            temp=-1;
        }
    }
    ll ans=inf;
    for(int i=0;i<256;i++) if(f[now][i][K]!=-1)
        down(ans,f[now][i][K]+s[i][255]-s[i][i]);
    printf("%lld\n",ans);

    return 0;
}

Problem E Need for Speed
bzoj4952

二分一下

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 inf 1e9+1
using namespace std;

const int maxn = 1100;
const double eps = 1e-9;

int n,t;
int di[maxn],si[maxn];

double cal(double mid)
{
    double re=0;
    for(int i=1;i<=n;i++)
    {
        double c=si[i]+mid;
        if(c<eps) return inf;
        re+=di[i]/c;
    }
    return re;
}

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

    scanf("%d%d",&n,&t);
    for(int i=1;i<=n;i++) scanf("%d%d",&di[i],&si[i]);

    double l=-inf,r=inf;
    while(r-l>eps)
    {
        double mid=(l+r)/2.0;
        if(cal(mid)>t) l=mid;
        else r=mid;
    }
    printf("%.10lf\n",l);

    return 0;
}

Problem D Money for Nothing
bzoj4951

问题相当于二维平面上在A类点中选一个和在B类点中选一个,要求A点在B点的左下方,求组成的矩形的最大面积

有用的A,B点一定都随着x增加y减小,观察可以发现这个最大面积满足决策单调性
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 inf 1e9
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';
}
inline void up(int &a,const int &b){if(a<b)a=b;}
inline void down(int &a,const int &b){if(a>b)a=b;}
const int maxn = 1010000;

int n,m;
struct node
{
    int x,y;
    friend inline bool operator <(const node x,const node y){return x.x<y.x;}
}a[maxn],b[maxn];
int L[maxn],ln,lc[maxn];
int R[maxn],rn,rc[maxn];

int tlp[maxn],tl[maxn],tln;
int trp[maxn],tr[maxn],trn;

ll ans;
void Solve(int l,int r,int ql,int qr)
{
    if(l>r) return;

    int mid=(l+r)>>1;
    ll tmp=LLONG_MIN,tmpi;
    for(int i=ql;i<=qr;i++) if(trp[mid]>tlp[i])
    {
        ll now=(ll)(trp[mid]-tlp[i])*(tr[mid]-tl[i]);
        if(tmp<now) tmp=now,tmpi=i;
    }
    if(ans<tmp) ans=tmp;
    Solve(l,mid-1,ql,tmpi);
    Solve(mid+1,r,tmpi,qr);
}

int main()
{
    read(n); read(m);
    for(int i=1;i<=n;i++) read(a[i].x),read(a[i].y),L[i]=a[i].x;
    for(int i=1;i<=m;i++) read(b[i].x),read(b[i].y),R[i]=b[i].x;

    sort(L+1,L+n+1); ln=0;
    for(int i=1;i<=n;i++) if(i==1||L[i]!=L[i-1]) L[++ln]=L[i],lc[ln]=inf;
    sort(a+1,a+n+1);
    for(int i=1,nl=0;i<=n;i++)
    {
        if(i==1||a[i].x!=a[i-1].x) nl++;
        down(lc[nl],a[i].y);
    }

    for(int i=1;i<=ln;i++)
    {
        if(tln&&tl[tln]<=lc[i]) continue;
        tln++;
        tlp[tln]=L[i],tl[tln]=lc[i];
    }

    sort(R+1,R+m+1); rn=0;
    for(int i=1;i<=m;i++) if(i==1||R[i]!=R[i-1]) R[++rn]=R[i],rc[rn]=0;
    sort(b+1,b+m+1);
    for(int i=1,nr=0;i<=m;i++)
    {
        if(i==1||b[i].x!=b[i-1].x) nr++;
        up(rc[nr],b[i].y);
    }

    for(int i=1;i<=rn;i++)
    {
        while(trn&&tr[trn]<=rc[i]) trn--;
        trn++;
        trp[trn]=R[i],tr[trn]=rc[i];
    }

    while(tln&&tlp[tln]>=trp[trn]) tln--;
    int pos=1;
    while(pos<=trn&&trp[pos]<=tlp[1]) pos++;
    for(int i=pos;i<=trn;i++) tr[i-pos+1]=tr[i],trp[i-pos+1]=trp[i];
    trn-=pos-1;

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

    return 0;
}

Problem C Mission Improbable
bzoj4950

先把俯视图上有箱子的格子都放一个,去掉俯视图的限制
只考虑正视图和侧视图就是只考虑行,列的最大值,最多拿多少=最少保留多少,每行每列一定要至少保留一个最大值,而一个最大值可以同时兼顾行列,行列之间最大值的带权匹配做个最小费用流(事实上因为不同最大值之间互不影响,直接二分匹配也行)

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 inf 1e18
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';
}
inline void up(int &a,const int &b){if(a<b)a=b;}
const int maxl = 110;
const int maxn = 1100;
const int maxm = 210000;

int n,m;
int ci[maxl][maxl],xl[maxn],xc[maxn];
struct edge{int y,c,d,nex;}a[maxm]; int len,fir[maxn];
inline void ins(const int x,const int y,const int c,const int d)
{
    a[++len]=(edge){y,c,d,fir[x]};fir[x]=len;
    a[++len]=(edge){x,0,-d,fir[y]};fir[y]=len;
}
struct Max_Flow
{
    void init()
    {
        len=1;
        for(int i=1;i<=n+m+2;i++) fir[i]=0;
    }
    int st,ed;
    ll dis[maxn];
    int pos[maxn],pre[maxn];
    queue<int>q; int insta[maxn];
    bool bfs()
    {
        for(int i=1;i<=ed;i++) dis[i]=inf;
        dis[st]=0; q.push(st);
        while(!q.empty())
        {
            const int x=q.front(); q.pop(); insta[x]=0;
            for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(a[k].c&&dis[y]>dis[x]+a[k].d)
            {
                dis[y]=dis[x]+a[k].d;
                pos[y]=x,pre[y]=k;
                if(!insta[y]) insta[y]=1,q.push(y);
            }
        }
        return dis[ed]!=inf;
    }
    ll Flow()
    {
        ll ans=0;
        while(bfs())
        {
            if(dis[ed]>0) break;
            ans+=dis[ed];
            for(int i=ed;i!=st;i=pos[i]) a[pre[i]].c--,a[pre[i]^1].c++;
        }
        return ans;
    }
}flow;

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

    read(n); read(m); ll sum=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            read(ci[i][j]); sum+=ci[i][j];
            up(xl[i],ci[i][j]);
            up(xc[j],ci[i][j]);
        }
    }

    ll ans=0;
    flow.init(); flow.st=n+m+1,flow.ed=n+m+2;
    for(int i=1;i<=n;i++) if(xl[i]) ins(flow.st,i,1,0);
    for(int i=1;i<=m;i++) if(xc[i]) ins(n+i,flow.ed,1,0);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) if(ci[i][j])
        {
            ans++;
            if(xl[i]==xc[j]) ins(i,n+j,1,-(xl[i]-1));
        }
    }
    for(int i=1;i<=n;i++) if(xl[i]) ans+=xl[i]-1;
    for(int i=1;i<=m;i++) if(xc[i]) ans+=xc[i]-1;
    ans+=flow.Flow();
    printf("%lld\n",sum-ans);

    return 0;
}

Problem B Get a Clue!
bzoj4949

爆搜…
一种比较naive的想法是枚举丢掉的是哪三张牌,再枚举其他三个人的卡牌,走一遍流程判是否合法,但这样很容易T
注意到事实上 S对于玩家A是否合法,与T对于玩家B是否合法 是互相独立的,所以我们可以先对每个玩家求出其合法的集合,再去做上面的爆搜

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;

const int maxn = 55;
const int mask = 1<<21;

int n;
int v0;
int qp[maxn],qi[maxn],rd[maxn][4];
int v[3][mask];

int judge(int x,int s)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=(qp[i]+1)&3;j!=qp[i];j=(j+1)&3)
        {
            if(j==x)
            {
                if(rd[i][j]==-1)
                {
                    if(s&qi[i]) return 0;
                }
                else
                {
                    if(qp[i]==0)
                    {
                        if(!(s>>rd[i][j]&1)) return 0;
                    }
                    else 
                    {
                        if(!(s&qi[i])) return 0;
                    }
                }
            }
            else if(rd[i][j]!=-1&&(qi[i]&s)==qi[i]) return 0;
            if(rd[i][j]!=-1) break;
        }
    }
    return 1;
}
int po(int i,int j,int k)
{
    int x=1<<i|1<<j|1<<k;
    if(x&v0) return 0;
    int Oth=(mask-1)^v0^x;
    for(int s=Oth;s;s=(s-1)&Oth) if(v[0][s])
    {
        int oth=Oth^s;
        for(int t=oth;t;t=(t-1)&oth) if(v[1][t])
            if(v[2][oth^t]) return 1;
    }
    return 0;
}
int ok[3][30];
void Print()
{
    int sum=0,la;
    for(int i=0;i<6;i++) if(ok[0][i]) sum++,la=i;
    putchar(sum==1?'A'+la:'?');
    sum=0;
    for(int i=0;i<6;i++) if(ok[1][i]) sum++,la=i;
    putchar(sum==1?'A'+6+la:'?');
    sum=0;
    for(int i=0;i<9;i++) if(ok[2][i]) sum++,la=i;
    putchar(sum==1?'A'+12+la:'?');
    putchar('\n');
}

char str[10];

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

    scanf("%d",&n);
    for(int i=0;i<5;i++)
    {
        scanf("%s",str); int x=str[0]-'A';
        v0|=1<<x;
    }
    qp[0]=3; memset(rd,-1,sizeof rd);
    for(int i=1;i<=n;i++)
    {
        qp[i]=(qp[i-1]+1)&3;
        for(int j=0;j<3;j++)
        {
            scanf("%s",str); int x=str[0]-'A';
            qi[i]|=1<<x;
        }
        for(int j=(qp[i]+1)&3;j!=qp[i];j=(j+1)&3)
        {
            scanf("%s",str);
            if(str[0]!='-') 
            {
                rd[i][j]=!qp[i]?str[0]-'A':0;
                break;
            }
        }
    }
    for(int i=0;i<mask;i++) if(!(i&v0))
    {
        int num=__builtin_popcount(i);
        if(num==5) v[0][i]=judge(1,i);
        if(num==4) v[1][i]=judge(2,i),v[2][i]=judge(3,i);
    }

    for(int i=0;i<6;i++) for(int j=0;j<6;j++) for(int k=0;k<9;k++) if(po(i,6+j,12+k))
        ok[0][i]=ok[1][j]=ok[2][k]=1;
    Print();

    return 0;
}

Problem A Airport Construction
bzoj4948

似乎是可以直接去枚举两个点,然后在多边形内延伸这两点间的连线
然而我计算几何相关都不会写…
于是我照着别人的程序写了一遍= =,这个代码貌似是把每条两点间的连线在多边形内被切割成的每一部分的长度都求了出来,我觉得实现的非常的妙

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<algorithm>
#define ll long long
#define ld long double
using namespace std;

const int maxn = 205;
const ld eps = 1e-8;

int n;
struct Point
{
    ld x,y;
    Point operator +(const Point &b){ return (Point){x+b.x,y+b.y}; }
    Point operator -(const Point &b){ return (Point){x-b.x,y-b.y}; }
    ld operator *(const Point &b)
    {
        return x*b.y-y*b.x;
    }
    inline ld Len() { return sqrt(x*x+y*y); }
}a[maxn];

struct Line
{
    Point a,b;
    Line(){}
    Line(const Point &_a,const Point &_b){a=_a,b=_b;}
    inline ld Dis(Line l)
    {
        return (l.a-a)*(l.b-a)/((l.b-l.a)*(b-a))*(b-a).Len();
    }
};
pair<ld,int>t[maxn]; int tp;
ld ans;

inline int Sgn(ld x){ return fabs(x)<eps?0:(x<0?-1:1); }
void Solve(Line l)
{
    tp=0;
    for(int i=1;i<=n;i++)
    {
        Line r=(Line){a[i-1],a[i]};
        int p=Sgn((l.b-l.a)*(r.a-l.a)),q=Sgn((l.b-l.a)*(r.b-l.a));
        if(p==q) continue;
        if(p>q) t[++tp]=make_pair(l.Dis(r),p&&q?2:1);
        else t[++tp]=make_pair(l.Dis(r),p&&q?-2:-1);
    }
    sort(t+1,t+tp+1);
    int cur=0; ld len=0;
    for(int i=1;i<=tp;i++)
    {
        if(cur) len+=t[i].first-t[i-1].first;
        else ans=max(ans,len),len=0;
        cur+=t[i].second;
    }
    ans=max(ans,len);
}

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

    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%Lf%Lf",&a[i].x,&a[i].y);
    a[n]=a[0];

    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++) Solve(Line(a[i],a[j]));
    printf("%.10Lf\n",ans);

    return 0;
}