2016 ACM-ICPC World Finals 部分题解

时间:2022-10-19 18:21:07

Problem A Balanced Diet

S = a i ,对于 n f i 1 < s i < n f i + 1 ,当nfi是非整数时有2个合法取值,nfi是整数时仅有1个合法取值,nfi是整数当且仅当n是S的倍数,此时所有糖果吃掉的个数是确定的(所以若k=aS+b,前aS天吃了什么可以无视掉),且吃掉的个数mod ai以S为循环节。
不妨设k< S,那么我们最多只需要吃到第S天,因为如果能吃到S天,就一定能一直吃下去
对于n=1 to S,第i种糖果的下界在1~ai间,求出到达每个下界 j 最早的时间t,在吃完t天后这个糖果一定要吃了至少 j 个,我们就可以做一个 O ( S + m ) 的贪心,对于经过的每一天,先不确定这天吃什么,到了某个糖果的下界 j ,如果这种糖果吃了不到 j 个,就在之前未确定吃什么的天数中取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<algorithm>
#define ll long long
#define pb push_back
using namespace std;

const int maxn = 110000;

int n,m,K;
int a[maxn],et[maxn];
vector<int>V[maxn];
int s[maxn],nowd[maxn];

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

    scanf("%d%d",&m,&K);
    for(int i=1;i<=m;i++) scanf("%d",&a[i]),n+=a[i];
    for(int i=1;i<=K;i++) scanf("%d",&et[i]);
    if(K>=n)
    {
        int nk=K%n;
        for(int i=1;i<=nk;i++) et[i]=et[K-(nk-i)];
        K=nk;
    }

    for(int i=1;i<=m;i++)
    {
        int c=a[i];
        for(int j=1;j<c;j++)
        {
            int t=(ll)n*j/c;
            if((ll)t*c<(ll)n*j) t++;
            V[t].push_back(i);
        }
        V[n].push_back(i);
    }

    for(int i=1;i<=K;i++)
    {
        s[et[i]]++;
        for(int j=0;j<(int)V[i].size();j++) nowd[V[i][j]]++;
    }
    int oth=0;
    for(int i=K+1;i<=n;i++)
    {
        oth++; int ok=1;
        for(int j=0;j<(int)V[i].size();j++)
        {
            int k=V[i][j]; nowd[k]++;
            if(s[k]<nowd[k])
            {
                if(!oth) { ok=0; break; }
                oth--; s[k]++;
            }
        }
        if(!ok) return printf("%d\n",i-K-1),0;
    }
    puts("forever");

    return 0;
}

Problem B Branch Assignment
之前写过这题的题解

Problem C Ceiling Function

直接暴力判就行了

Problem D Clock Breaking

感觉可以枚举时间,用bitset加速匹配,似乎是个大讨论+细节题于是我跑了…

Problem E Forever Young

当转化成的b进制数位数<4时,因为要求转化完每一位0~9,可以枚举转化完的数,解个方程求出是否存在对应的b,当位数>=4时,b<=100w,可以直接枚举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<algorithm>
#define ll long long
#define ld long double
using namespace std;

const int maxn = 100;
const ld eps = 1e-9;

ll y,d,ub;
ll tl[maxn],tlp,t[maxn],tp;
int Big()
{
    if(tp!=tlp) return tp>tlp;
    for(int i=tp;i>=1;i--) if(t[i]!=tl[i])
        return t[i]>tl[i];
    return 1;
}
void get_ub()
{
    ll tmp;
    tmp=d; while(tmp) tl[++tlp]=tmp%10ll,tmp/=10ll;

    ll l=2,r=y;
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        tmp=y;
        tp=0; while(tmp) t[++tp]=tmp%mid,tmp/=mid;
        if(Big()) l=mid+1ll;
        else r=mid-1ll;
    }
    ub=l-1ll;
}

ll Find(ll i)
{
    if(i>=100)
    {
        ll a=i/100,b=(i/10)%10,c=i%10; c-=y;
        ld delta=(ld)b*b-4.0*(ld)a*(ld)c;
        if(delta>-eps)
        {
            delta=max(delta,(ld)0);
            delta=sqrt(delta);
            ld a0=(-(ld)b+delta)/(ld)(2.0*a);
            ld a1=(-(ld)b-delta)/(ld)(2.0*a);
            ll b0=(ll)(a0+0.5);
            ll b1=(ll)(a1+0.5);

            if(b0<=1||b0>ub||a*b0*b0+b*b0+c!=0) b0=-1;
            if(b1<=1||b1>ub||a*b1*b1+b*b1+c!=0) b1=-1;
            return max(b0,b1);
        }
        return -1;
    }
    else
    {
        ll a=i/10,b=i%10;
        b=y-b;
        return b%a==0?(b/a>ub?-1:b/a):-1;
    }
}
ll b;

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

    scanf("%lld%lld",&y,&d);
    get_ub();

    for(ll i=10;i<1000;i++) 
        b=max(b,Find(i));
    for(ll i=2;i<=ub&&i<=1000000;i++)
    {
        int ok=1;
        for(ll p=1;ok&&p<y&&p>0;p*=i)
        {
            if((y/p)%i>9ll) ok=0;
            ld np=(ld)p*(ld)i;
            if(np>(ld)y-eps) break;
        }
        if(ok) b=max(b,i);
    }
    printf("%lld\n",b);

    return 0;
}

Problem F Longest Rivers

问题相当于每次给出一个询问L,问最少有多少条河流,长度一定>L
假设只有一个询问,我们可以对这棵树从下往上做一个贪心:若一个点所有连入的河流长度都<=L,就把他的父边分配给连入他的最短的河流,否则随便选一个长度>L的河流分配

当一个点连向父亲的河流长度>L时我们定义这个点是黑点,否则定义他为白点
求最少有多少条河流长度>L就是求最少有多少个孩子全是白点(或者没有孩子)的黑点

考虑对于n个L从小到大求这个东西
一开始L=0,所有点都是黑点
随着L逐渐增大,会有一些黑点变成白点,我们只要对每个黑点维护他在 L >= t 时变成白点,放进一个堆里,处理到每个L时把<=L的 t 都取出来处理即可
但是注意到我们不能直接对所有黑点去维护这个东西,因为一个点黑变白时他返回给父亲的值可能发生变化,进而可能影响他的 O ( n ) 个祖先的值,我们不可能对所有祖先都修改他的 t
有个性质是一个点是黑点那么他的父亲一定是黑点,于是如果一个黑点 p 有至少一个儿子是黑点,我们就没有必要去维护 p t 值,我们其实只需要将所有儿子都是白点的黑点的 t 放进堆里,每次一个点黑变白后,如果他的父亲所有儿子都是白点,就枚举一遍他父亲的所有儿子算出他父亲的 t 塞进堆里
处理一个询问的L时,堆中的元素个数恰好就是要求的最少有多少个孩子全是白点的黑点
这样做每个点入堆出堆仅一次,复杂度 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<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';
}
inline void down(ll &a,const ll &b){if(a>b)a=b;}
const int maxn = 1010000;

int n,m;
char _name[maxn][15];
int ans[maxn],fa[maxn];

struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}
ll d[maxn],val[maxn];
int id[maxn];
inline bool cmp(const int x,const int y){return d[x]<d[y];}

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 mn[maxn];
int siz[maxn];
void dfs(const int x)
{
    siz[x]=0;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
    {
        d[y]=d[x]+val[y];
        dfs(y);
        siz[x]++;
    }
    if(!siz[x]) q.push((node){val[x],x});
}

void Upd(int tx)
{
    mn[tx]+=val[tx];

    if(tx==n+1) return;
    int x=fa[tx]; siz[x]--;
    if(siz[x]) return;

    mn[x]=LLONG_MAX;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
        down(mn[x],mn[y]);
    q.push((node){mn[x]+val[x],x});
}

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

    read(n); read(m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",_name[i]);
        int y,c; read(y); read(c);
        fa[i]=n+1+y; val[i]=c;
        ins(fa[i],i);
    }
    for(int i=1;i<=m;i++)
    {
        int y,c; read(y); read(c);
        fa[n+1+i]=n+1+y; val[n+1+i]=c;
        ins(fa[n+1+i],n+1+i);
    }
    dfs(n+1);

    for(int i=1;i<=n;i++) id[i]=i;
    sort(id+1,id+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        ll nowt=d[id[i]];
        while(!q.empty())
        {
            const node now=q.top();
            if(nowt<now.x) break;
            q.pop(); Upd(now.i);
        }
        ans[id[i]]=q.size();
    }

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

    return 0;
}

Problem G Oil

最终直线一定过一条线段的左端点和一条线段的右端点,枚举左端点,其他线段的两个端点相对于这个左端点有个的斜率区间,排序扫一遍, O ( n 2 l o g n ) ,斜率用x/y,不然会有不合法和卡精等问题

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 = 2100;
const ld eps = 1e-7;

int Sgn(ld x){ return fabs(x)<eps?0:(x>0?1:-1); }

int n;
struct point
{
    ld x,y;
    friend inline point operator -(const point &a,const point &b){return (point){a.x-b.x,a.y-b.y};}
    inline ld rad(){ return x/y; }
};
struct Line
{
    point a,b;
    ld val;
}L[maxn];

struct node
{
    int i,ad;
    ld rad;
    friend inline bool operator <(const node x,const node y)
    {
        int Sig=Sgn(x.rad-y.rad);
        if(!Sig) return x.ad>y.ad;
        return Sig<0;
    }
}a[maxn<<1]; int cnt;
ld ans;
void Upd(ld now)
{
    for(int i=1;i<=cnt;i++)
    {
        if(a[i].ad==1) now+=L[a[i].i].val;
        else now-=L[a[i].i].val;
        ans=max(ans,now);
    }
}

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

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        ld l,r,h; scanf("%Lf%Lf%Lf",&l,&r,&h); if(Sgn(l-r)>0) swap(l,r);
        L[i]=(Line){(point){l,h},(point){r,h}};
        L[i].val=r-l;
        ans=max(ans,L[i].val);
    }

    for(int i=1;i<=n;i++)
    {
        cnt=0;
        for(int j=1;j<=n;j++) if(Sgn(L[i].a.y-L[j].a.y))
        {
            ld r1=(L[j].a-L[i].a).rad(),r2=(L[j].b-L[i].a).rad();
            if(Sgn(r1-r2)>0) swap(r1,r2);
            a[++cnt]=(node){j,1,r1};
            a[++cnt]=(node){j,-1,r2};
        }
        sort(a+1,a+cnt+1);
        Upd(L[i].val);
    }
    printf("%.0Lf\n",ans);

    return 0;
}

Problem H Polygonal Puzzle

感觉有点毒,跑了跑了

Problem I Road Times

直接跑单纯形就行了
论单纯形优化的几种姿势…
我的单纯形板子在cf上都T了….
然后去Orz了杜老师的代码
几个提速的地方

第一个while里,松弛形式 y i + a 1 x 1 + a 2 x 2 . . . . <= b b 通过转轴转正那里找 b 的绝对值最大的那一行 i ,在那一行里找 a j 最大的那一个 j i 转轴
第二个while里,答案式 a 1 x 1 + a 2 x 2 . . . . . + b ,找一个正的 a i 增大 x i 那一部,找 a i 绝对值最大的那个 i b
这个东西比随机化快很多(至少在这一题是)

然后在转轴(pivot)操作里
对于转的那一行 l ,记录他的哪些位置非0,更新矩阵时只扫非0位

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 pb push_back
#define fir first
#define sec second
#define mp make_pair
using namespace std;

const int maxn = 500;
const double eps = 1e-6;
const double inf = 1e20;

vector< pair<int,int> >c[maxn]; int tot;
namespace Simplex
{
    int n,m,pn;
    int idx[maxn],idy[maxn];
    double a[maxn][105];

    void init()
    {
        m=tot;
        for(int i=0;i<=m;i++) for(int j=0;j<=n;j++) a[i][j]=0;
        for(int i=1;i<=m;i++)
        {
            a[i][0]=c[i][0].sec;
            for(int j=1;j<(int)c[i].size();j++) a[i][c[i][j].fir]=-c[i][j].sec;
        }
    }
    int t[maxn],tp;
    void pivot(const int &e,const int &l)
    {
        double tc=-a[l][e];
        for(int i=0;i<=n;i++) if(i!=e) a[l][i]/=tc;
        a[l][e]=-1/tc;

        tp=0; for(int i=0;i<=n;i++) if(fabs(a[l][i])>eps&&i!=e) t[++tp]=i;

        for(int i=0;i<=m;i++) if(i!=l)
        {
            for(int j=1;j<=tp;j++) a[i][t[j]]+=a[i][e]*a[l][t[j]];
            //for(int j=0;j<=n;j++) if(j!=e) a[i][j]+=a[i][e]*a[l][j];
            a[i][e]*=a[l][e];
        }
        swap(idx[e],idy[l]);
        ++pn;
    }
    int type;
    double ans[maxn];
    double simplex()
    {
        for(int i=1;i<=n;i++) idx[i]=i;
        for(int i=1;i<=m;i++) idy[i]=n+i;

        int x,y;
        while(1)
        {
            x=y=0;
            for(int i=1;i<=m;i++) if(a[i][0]<-eps&&(!y||a[i][0]<a[y][0])) y=i;
            if(!y) break;
            for(int i=1;i<=n;i++) if(a[y][i]>eps&&(!x||a[y][i]>a[y][x])) x=i;
            if(!x) { puts("Infeasible");return 0.0; }
            pivot(x,y);
        }

        while(1)
        {
            x=y=0;
            for(int i=1;i<=n;i++) if(a[0][i]>eps&&(!x||a[0][i]>a[0][x])) { x=i;break; }
            if(!x) break;
            double temp=inf;
            for(int i=1;i<=m;i++) if(a[i][x]<-eps)
            {
                double cc=-a[i][0]/a[i][x];
                if(cc<temp) temp=cc,y=i;
            }
            if(!y) { puts("Unbounded");return 0.0; }
            pivot(x,y);
        }

        return a[0][0];
        //if(!type) return;
        //for(int i=1;i<=m;i++) if(idy[i]<=n) ans[idy[i]]=a[i][0];
        //for(int i=1;i<=n;i++) printf("%.10lf ",ans[i]);
        //putchar('\n');
    }
}
int n,r,Q;
int id[40][40],val[40][40],cnt;
int dis[40][40]; vector<int>pat[40][40]; 
int vis[maxn],S,T;
void Path()
{
    scanf("%d%d",&S,&T); S++; T++;
    for(int i=1;i<=cnt;i++) vis[i]=0;
    for(int i=0;i+1<(int)pat[S][T].size();i++)
        vis[id[pat[S][T][i]][pat[S][T][i+1]]]=1;
}

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

    scanf("%d",&n);
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
    {
        int x; scanf("%d",&x); if(x!=-1&&i!=j)
        {
            id[i][j]=++cnt;
            val[i][j]=x;
            dis[i][j]=x;
        }
        else dis[i][j]=1e9;
    }
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
    {
        pat[i][j].pb(i);
        if(j!=i) pat[i][j].pb(j);
    }
    for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) if(i!=k) for(int j=1;j<=n;j++) if(i!=j&&k!=j)
        if(dis[i][j]>dis[i][k]+dis[k][j])
        {
            dis[i][j]=dis[i][k]+dis[k][j];
            pat[i][j].clear();
            for(int l=0;l<(int)pat[i][k].size();l++) pat[i][j].pb(pat[i][k][l]);
            for(int l=1;l<(int)pat[k][j].size();l++) pat[i][j].pb(pat[k][j][l]);
        }

    Simplex::n=cnt;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(id[i][j])
    {
        ++tot; c[tot].pb(mp(0,-val[i][j])); c[tot].pb(mp(id[i][j],-1));
        ++tot; c[tot].pb(mp(0,val[i][j]*2)); c[tot].pb(mp(id[i][j],1));
    }

    scanf("%d",&r);
    for(int i=1;i<=r;i++)
    {
        Path(); int ut; scanf("%d",&ut);
        ++tot; c[tot].pb(mp(0,-ut));
        for(int j=1;j<=cnt;j++) if(vis[j]) c[tot].pb(mp(j,-1));
        ++tot; c[tot].pb(mp(0,ut));
        for(int j=1;j<=cnt;j++) if(vis[j]) c[tot].pb(mp(j,1));
    }
    //double t3=clock();
    //cout << t3 <<endl;

    scanf("%d",&Q);
    for(int i=1;i<=Q;i++)
    {
        Path(); printf("%d %d ",S-1,T-1);

        Simplex::init();
        for(int j=1;j<=cnt;j++) if(vis[j]) Simplex::a[0][j]=-1.0;

        printf("%.10lf ",-Simplex::simplex());

        Simplex::init();
        for(int j=1;j<=cnt;j++) if(vis[j]) Simplex::a[0][j]=1.0;

        printf("%.10lf\n",Simplex::simplex());
    }
    //printf("%d\n",Simplex::pn);
    //double t4=clock()-t3;
    //cout << t4 << endl;
    return 0;
}

Problem J Spin Doctor

这题做到倦生…
将每个人视作一个二维平面上的点,将ci为true的人视为黑点,false的视为白点,那么选定一组(S,T)排序就是选定一个倾斜角,用一对平行线去夹住所有黑点,这对平行线之间的所有点就是k-j+1
对黑点建出凸包,显然最优解是用平行线去卡这个凸包,问题是怎么算平行线间的点数
对于凸包内(上)的点,他们一定会被算进去
对于凸包外的点,找到他和凸包的两条切线,当平行线在这两条切线之间时这个点会被算到,找到所有区间后做一次扫描线

判点在不在凸包内,可以先二分判断点在哪个区间内
2016 ACM-ICPC World Finals 部分题解
再判点在不在凸包内

怎么找点和凸包的切线呢
设我们现在要求点A和凸包的切线,我们先在凸包上随便找一个点B,设凸包点的编号逆时针递增,不妨设B的编号是1
2016 ACM-ICPC World Finals 部分题解
A和B的连线会将凸包分成两部分,分界点可以二分出来(A->B和B->2~n的叉积是在分界点前面一段正的,分界点后面是负的)
然后在半个凸包上,设编号是[L,R],对于i=L~R-1,A->B和边(i,i+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
#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';
}
int Sgn(ll x) { return x==0?0:(x>0?1:-1); }
const int maxn = 510000;
const ld eps = 1e-12;

int n,base,Ans;
struct Point
{
    ll x,y;
    friend inline Point operator +(const Point &a,const Point &b){return (Point){a.x+b.x,a.y+b.y};}
    friend inline Point operator -(const Point &a,const Point &b){return (Point){a.x-b.x,a.y-b.y};}
    friend inline ll operator *(const Point &a,const Point &b){return a.x*b.y-a.y*b.x;}
    friend inline bool operator ==(const Point &a,const Point &b){return a.x==b.x&&a.y==b.y;}
    ld Len(){ return sqrt((ld)x*x+(ld)y*y); }
    void Read(){int t;scanf("%d",&t);x=t;scanf("%d",&t);y=t;}
    void print(){printf("%lld %lld\n",x,y);}
}a[maxn],b[maxn],zero; int an,bn;
inline bool cmp(const Point &x,const Point &y) 
{
    ll tc=(x-a[1])*(y-x);
    return tc==0?((x-a[1]).Len()<(y-a[1]).Len()):tc>0;
}

Point vst=(Point){0,-1};
struct data
{
    Point x; int c;
    friend inline bool operator <(const data &x,const data &y){ return x.x*y.x>0; }
}v[maxn]; int vn,now0;

struct Convex_Hull
{
    Point c[maxn]; int cn;
    void build_convex()
    {
        for(int i=2;i<=an;i++) if(a[i].y<a[1].y||(a[i].y==a[1].y&&a[i].x<a[1].x))
            swap(a[1],a[i]);
        for(int i=1;i<=bn;i++) b[i]=b[i]-a[1];
        for(int i=2;i<=an;i++) a[i]=a[i]-a[1];
        a[1]=(Point){0,0};
        sort(a+2,a+an+1,cmp);

        cn=0;
        for(int i=1;i<=an;i++)
        {
            while(cn>1&&(c[cn]-c[cn-1])*(a[i]-c[cn])<=0) cn--;
            c[++cn]=a[i];
        }
        while(cn>2&&(c[cn]-c[cn-1])*(c[1]-c[cn])<=0) cn--;
    }
    void get_convex()
    {
        build_convex();
        if(cn==1) { printf("%d\n",base); exit(0); }
        if(cn==2)
        {
            for(int i=1;i<=bn;i++)
            {
                if((b[i]-c[cn])*(c[cn]-c[1])==0&&
                fabs((b[i]-c[1]).Len()+(b[i]-c[cn]).Len()-(c[1]-c[cn]).Len())<=eps)
                    Ans++;
            }
            printf("%d\n",base+Ans);
            exit(0);
        }
    }
    bool In_Convex(Point x)
    {
        if(x.y<0) return false;
        if((c[cn]-c[1])*(x-c[cn])>0) return false;
        int l=2,r=cn;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if((c[mid]-c[1])*(x-c[mid])<=0) r=mid-1;
            else l=mid+1;
        }++r;
        ll cs=(c[r]-c[r-1])*(x-c[r]);
        if(cs==0&&fabs((x-c[r]).Len()+(x-c[r-1]).Len()-(c[r]-c[r-1]).Len())>eps) cs=-1; 
        return cs>=0;
    }
    void Add(Point x,Point l,Point r)
    {
        if((r-x)*(l-r)>0) swap(l,r);
        //int sp=0; if(l.x<r.x||(l.x==r.x&&l.y<r.y)) sp=1,swap(l,r);
        //x.print();l.print();r.print();
        //Point x1,l1,r1;x1.Read(); l1.Read();r1.Read();
        //if(l*l1!=0) puts("qaq");
        //if(r*r1!=0) puts("qaq");

        //if(sp) swap(l,r);
        if(l.x<0) l=zero-l,r=zero-r;

        if(r.x>=0) 
        {
            if(l.x!=0) v[++vn]=(data){l,1};
            else now0++;
            if(r.x!=0) v[++vn]=(data){r,-1};
        }
        else
        {
            r=zero-r;
            now0++; v[++vn]=(data){r,-1};
            v[++vn]=(data){l,1};
        }
    }
    Point Find(Point x,int L,int R)
    {
        Point re=c[L]-x;
        if(L<R)
        {
            int l,r;
            ll tc=(c[1]-x)*(c[L+1]-c[1]);
            if(!tc) tc=(c[1]-x)*(c[L]-c[1]);
            if(tc<0)
            {
                for(l=L+1,r=R;l<=r;)
                {
                    int mid=(l+r)>>1;
                    if((c[mid-1]-x)*(c[mid]-c[mid-1])<=0) l=mid+1;
                    else r=mid-1;
                }l--;
            }
            else
            {
                for(l=L+1,r=R;l<=r;)
                {
                    int mid=(l+r)>>1;
                    if((c[mid-1]-x)*(c[mid]-c[mid-1])>=0) l=mid+1;
                    else r=mid-1;
                }l--;
            }
            re=c[l]-x;
        }
        return re;
    }
    void Query(Point x)
    {
        if(In_Convex(x)) { base++;return; }

        int p1=1,p2,sig=Sgn((c[1]-x)*(c[2]-c[1])),l,r;

        for(l=2,r=cn;l<=r;)
        {
            int mid=(l+r)>>1;
            if(Sgn((c[1]-x)*(c[mid]-c[1]))==sig) l=mid+1;
            else r=mid-1;
        }p2=--l;

        if(p2==cn) Add(x,Find(x,p1,p1),Find(x,2,p2));
        else Add(x,Find(x,p1,p2),Find(x,p2+1,cn));
    }
}ch;

void Solve()
{
    for(int i=1;i<=bn;i++) ch.Query(b[i]);
    v[++vn]=(data){vst,0};
    sort(v+1,v+vn+1);

    int now=now0; Ans=now;
    for(int i=1;i<=vn;)
    {
        int j=i+1;for(;j<=vn&&v[i].x*v[j].x==0;j++);j--;
        for(;i<=j;i++) now+=v[i].c;
        Ans=min(Ans,now);
    }
}

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

    zero=(Point){0,0};

    read(n);
    for(int i=1;i<=n;i++)
    {
        int x,y,c;read(x);read(y);read(c);
        if(c) a[++an]=(Point){x,y};
        else b[++bn]=(Point){x,y};
    }
    base=an;

    ch.get_convex();

    Solve();
    printf("%d\n",Ans+base);

    return 0;
}

Problem K String Theory

有个结论是一个k-quotation序列一定是一个k-quotation

证明可以用归纳证:
假设已经证明了< k成立,对于当前的k,我们只要证明两个相邻的k-quotation,同时也会是一个k-quotation,就能再用一次归纳证明对于当前的k成立
对于两个相邻的,一定是这样的
k , k 1 q u o t a t i o n , k , k , k 1 q u o t a t i o n , k
逗号的位置可以是某个字符串隔开两边,也可以没有东西两边的引号挨在一起
因为结论对于k-1成立,同时两端各有k个引号,我们只要证明去掉了两端的k,剩下部分是一个k-1-quotation
剩下部分是
k 1 , k 2 q u t a t i o n , k 1 , k , k , k 1 , k 2 q u o t a t i o n , k 1
我们可以仿照上面的步骤,去掉两端的k-1,再把问题递归下去
最后是这样的序列: 1 , 2 , . . . . . . k 1 , k , k , k 1......2 , 1 ,他一定是个1-quotation,于是命题得证

有了这个结论就可以枚举k,O(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 = 1100;

int n;
int a[maxn],sum[maxn],f[maxn];

int judge(int now)
{
    for(int i=1;i<=n;i++) f[i]=a[i];
    int l=1,r=n;
    for(;now>1&&l<=r;now--)
    {
        if(f[l]<now) break;
        f[l]-=now; if(!f[l]) l++;
        if(f[r]<now) break;
        f[r]-=now; if(!f[r]) r--;
    }
    return l<=r&&now==1&&(l==r?f[l]:f[l]+f[r]+sum[r-1]-sum[l])%2==0;
}

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

    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    if(sum[n]&1) return puts("no quotation"),0;

    for(int i=min(a[1],a[n]);i>=2;i--) if(judge(i))
        return printf("%d\n",i),0;
    if(sum[n]==2) puts("1");
    else puts("no quotation");

    return 0;
}

Problem L Swap Space

分成a< b,a>=b两类,贪心排序

Problem M What Really Happened on Mars?

不知道题在说什么也不知道题解在说什么也不知道为什么照着题解说的打了WA掉了……