zoukankan      html  css  js  c++  java
  • 劳动节CF题总结

    注:题意略

    (vjudge上有中文翻译)

    (可能会更新,因为我可能会再做点题)

    感觉 CF 题对思维的训练意义很大,并且部分题对码力的训练也相当不错(WA自闭了)

    CF1458E Nim Shortcuts

    这就是那个WA自闭的题,最后都开始骗数据了

    我们把状态 ((x,y)) 看成平面直角坐标系上的点 ((x,y)) 。那必输的点集相当于直线 (y=x) 上的点。我们记先手必输的点集为 (S)

    考虑加入了某个“捷径” ((x_i,y_i)),它会对 (S) 产生什么影响呢?

    我们知道,有这样一个显然的结论

    如果一个游戏状态的所有后继状态都是必胜状态,那么该状态为必输状态;

    反之,只要有一个后继状态是必输状态,那么该状态为必胜状态

    对于某个捷径 ((x_i,y_i)),显然,它会让同一行、同一列的,x/y 坐标值比它大的所有点变成先手必胜。

    显然,因为先手可以一步走到 ((x_i,y_i)) 取胜

    我们用 WPS 表格画出这个状态,红色格子表示先手必输,绿色格子表示先手必胜;红色格子里标 “S” 的表示它要么是 ((0,0)) (最左上角),要么是一个捷径。

    根据上面那个“显然的结论”,我们可以递推出整张图所有格子的胜负性

    (我当时就是这么打草稿的)

    我们发现,加入一个新的捷径以后,由于它强行让它所在那一行、列的后面变成了绿色,原本一条红色的斜线的一部分被迫偏移了,向下偏了一格。体现在坐标系上,即 x/y 坐标被迫 +1。

    如果您有兴致,可以多加几个捷径画一画(像我一样),然后就会发现,多几个捷径情况还是差不多的,只是这条线可能会偏移不止一格(因为捷径很多)。

    那现在我们大概知道这个“必输线”怎么搞了:默认斜着走,如果同一行/列的前面有捷径就偏移;这样走得到的轨迹就是“必输线”,即集合 (S)

    这里我们判断它是否会“偏移”,要用 map 记下每一行的捷径中最小的 (x) 坐标,和每一列的捷径中最小的 (y) 坐标,如果比当前点的 x/y 坐标来的小,就要偏移一下。

    由于坐标范围是 (1e9) 的,所以每次要 lower_bound 找一下,看跳到哪里,而不能一个一个跳。

    如何存储这个红色的“必输线”呢?我们发现,它是由若干个斜率为 (1) 的线段组成的,只是截距((y-x))不同。而且它至多只会有 (n) 段,因为一个捷径最多让它多一段,所以总共最多也不会超过 (n) 段。

    于是我们可以按截距分类。对于每一种截距,我们存 (x) 的取值范围。范围可能是多段区间并起来,所以我们要用一个 vector 来存。截距的范围很广,但是很少,还要再来一个 map。设 node 表示一个区间,那么我们使用的数据结构为:map<int,vector<node> >。由于总共不超过 (n) 段,所有 vectorsize 的和也不会超过 (n)。这样预处理,时间复杂度 (O(nlog n)),空间复杂度 (O(n))

    我是在刘汝佳的蓝书中见到这样的阴间结构的

    此处为啥不离散化:会影响直线方程的计算

    那对于每次询问 ((x,y)),把 (y-x) 的截距拿出来,然后得到一个 vector<node ,判断一下 (x) 是否在里面即可,这个东西也很好二分解决。于是每次询问就是 (log) 的。注意判一下初始位置就在捷径的情况。

    一个细节:每次跳的时候,先处理“偏移”,再开始跳,因为你可能从 ((1,1)) 就开始偏移。

    另一个细节:while 循环处理偏移的时候,不要先搞 (x) 再搞 (y),详情见代码。

    一个注意:注意一下常数

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    namespace Flandre_Scarlet
    {
        #define N 100005
        #define INF 0x3f3f3f3f
        #define F(i,l,r) for(int i=l;i<=r;++i)
        #define D(i,r,l) for(int i=r;i>=l;--i)
        #define Fs(i,l,r,c) for(int i=l;i<=r;c)
        #define Ds(i,r,l,c) for(int i=r;i>=l;c)
        #define MEM(x,a) memset(x,a,sizeof(x))
        #define FK(x) MEM(x,0)
        #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
        #define p_b push_back
        #define sz(a) ((int)a.size())
        #define all(a) a.begin(),a.end()
        #define iter(a,p) (a.begin()+p)
        #define PUT(a,n) F(i,1,n) printf("%d ",a[i]); puts("");
        int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
        template <typename T> void Rd(T& arg){arg=I();}
        template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
        void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    
        int n,m;
        struct node{int x,y;}; // node(x,y) 有多种含义, 有时代表一个点, 有时代表一个区间
        bool operator<(node a,node b) {return a.x<b.x or (a.x==b.x and a.y<b.y);}
        node s[N];
        void Input()
        {
            Rd(n,m);
            s[0]=(node){0,0}; 
            F(i,1,n) 
            {
                s[i]=(node){I(),I()};
                if (s[i].x<=5 and s[i].y<=5)
                {
                    printf("(%d,%d)
    ",s[i].x,s[i].y);
                }
            }
        }
        map<int,vector<node> > range;
        map<node,bool> have;
        map<int,int> firx,firy;
        // 先手败的点一定在若干条 y-x=k 的线段上
        // range[k]: 对于所有 y-x=k 的线段, x 的取值范围
        // 因为可能是若干个区间的并, 所以用 vector
        int xp[N],yp[N]; // 记录下所有的x,y坐标(方便跳)
        bool in_range(vector<node> &R,int p) // 判断 p 是否在 R 所代表的范围中
        {
            if (R.empty()) return false;
            if (R[0].x>p) return false;
            if (R.back().y<p) return false;
            node las=*(upper_bound(all(R),(node){p,INF})-1);
            // 最后一个左端点<=p的区间
            if (las.y>=p) return true;
            return false;
        }
        void Sakuya()
        {
            F(i,1,n) // 预处理: 每行第一个,每列第一个
            {
                xp[i]=s[i].x,yp[i]=s[i].y;
                have[s[i]]=1;
                if (firy.find(s[i].x)==firy.end())
                {
                    firy[s[i].x]=s[i].y;
                }
                else
                {
                    firy[s[i].x]=min(firy[s[i].x],s[i].y);
                }
    
                if (firx.find(s[i].y)==firx.end())
                {
                    firx[s[i].y]=s[i].x;
                }
                else
                {
                    firx[s[i].y]=min(firx[s[i].y],s[i].x);
                }
            }
    
            xp[n+1]=INF; yp[n+1]=INF; // 哨兵
            sort(xp+1,xp+n+1); sort(yp+1,yp+n+1);
            node cur=(node){0,0}; // 从 (0,0) 开始
            while(1) // 均摊 O(nlogn)
            {
                while(1) 
                {
                    bool flag=0;
                    if (firy.count(cur.x) and firy[cur.x]<=cur.y) ++cur.x,flag=1;
                    if (firx.count(cur.y) and firx[cur.y]<=cur.x) ++cur.y,flag=1;
                    if (!flag) break;
                }
                /*
                注:
                while(要偏x) ++cur.x;
                while(要偏y) ++cur.y;
                这样的写法是错误的, 因为处理完偏y之后, y坐标增加, 可能又有x要偏移了
                比如这样的数据 (test#6 的一部分)
                捷径: (1,0) (2,2) (3,0) (4,2) (5,2)
                */ 
    
                int b=cur.y-cur.x;
                if (!range.count(b))
                {
                    range[b]=vector<node>();
                } // 初始化一下, 防止阴间问题
                int nx=*upper_bound(xp+1,xp+n+2,cur.x);
                int ny=*upper_bound(yp+1,yp+n+2,cur.y);
                if (nx>1e9 and ny>1e9) // 最后一段
                {
                    range[b].p_b((node){cur.x,INF}); 
                    // 只要我们不停下来, x坐标就会不断延伸, 所以说, 不要停下来啊 (无端)
                    // 容易发现它确实是不断延伸的
                    break;
                }
                else
                {
                    int d=min(nx-cur.x,ny-cur.y);
                    range[b].p_b((node){cur.x,cur.x+d-1});
                    cur.x+=d; cur.y+=d;
                }
            }
    
            F(i,1,m)
            {
                int x=I(),y=I();
                if (have[(node){x,y}] or (x==0 and y==0))
                {
                    puts("LOSE");
                    continue;
                }
                int b=y-x;
                if (in_range(range[b],x))
                {
                    puts("LOSE");
                }
                else
                {
                    puts("WIN");
                }
            }
        }
        void IsMyWife()
        {
            Input();
            Sakuya();
        }
    }
    #undef int //long long
    int main()
    {
        freopen("in.txt","r",stdin);
        Flandre_Scarlet::IsMyWife();
        getchar();
        return 0;
    }
    

    CF1500D Tiles for Bathroom

    有一个显然的思路,肯定是钦定一个角,求出最长能扩展的边长 (L),然后对答案数组 ([1,L]) 做区间 (+1)

    一开始我们可能会想到枚举左上角,然后像manacher一样继承一下右下角

    但是我们发现它继承不起来,每次继承就要重新算一下出现次数的数组,并不能直接继承一部分。

    (做到这我睡着了)

    当你发现枚举一边不好用,那可以试试枚举另一边。(我醒来之后突然想到)考虑枚举右端点。注意到 (q) 很小,只要维护最近的 (q)颜色 即可(换句话说要对相同颜色的去个重)。这样就可以把上面,左边,左上三个位置的颜色(一共 (3q) 个)合并一下得到当前离的“最近”的 (q) 个颜色。

    直观上感觉很对,然而位置的坐标有两个数,取什么样的“最近”?

    回到原问题,我们要用正方形来盖住这些颜色。所以,“距离”就是"至少需要多少边长的正方形才能覆盖“。由于 (x,y) 两个坐标都要覆盖到,所以是 (x,y) 坐标的差取 (max)。那其实就是切比雪夫距离。

    然后我们要求“最长扩展长度”,其实可以求"最短不合法长度“然后-1。”最短不合法长度“就是第 (q+1) 近的颜色的切比雪夫距离。

    直接求第 (q) 近的颜色会有问题,就比如第 (q) 近的颜色切比雪夫距离为 (k),但是可能 (q+1) 近的颜色切比雪夫距离也是 (k)。此时如果覆盖一个边长为 (k) 的正方形,那就会覆盖到第 (q+1) 近的那个,从而导致存在多于 (q) 种颜色。

    其实样例 (2) 就是一个反例,如果直接取第 (q+1) 近的,那你第四个数会输出 (1)

    然后就随便做了。复杂度是 (O(n^2qlog q)),稍微卡下常。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    namespace Flandre_Scarlet
    {
        #define N 1502
        #define F(i,l,r) for(int i=l;i<=r;++i)
        #define D(i,r,l) for(int i=r;i>=l;--i)
        #define Fs(i,l,r,c) for(int i=l;i<=r;c)
        #define Ds(i,r,l,c) for(int i=r;i>=l;c)
        #define MEM(x,a) memset(x,a,sizeof(x))
        #define FK(x) MEM(x,0)
        #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
        #define p_b push_back
        #define sz(a) ((int)a.size())
        #define all(a) a.begin(),a.end()
        #define iter(a,p) (a.begin()+p)
        #define PUT(a,n) F(i,1,n) printf("%d ",a[i]); puts("");
        int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
        template <typename T> void Rd(T& arg){arg=I();}
        template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
        void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
        int n,q;
        int a[N][N];
        void Input()
        {
            Rd(n,q);
            F(i,1,n) F(j,1,n)
            {
                a[i][j]=I();
            }
        }
        struct node
        {
            int x,y,d; // d: 切比雪夫距离
        };
        bool cmp_d(node a,node b) {return a.d<b.d;}
        vector<node> rec[N][N];
        node t[35]; // 这个t用vector会TLE
        int mxlen[N][N];
        inline int cheb(int x1,int y1,int x2,int y2) {return max(abs(x1-x2),abs(y1-y2))+1;}
        bool vis[N*N]; // 记下颜色, 每次用完要清理一下
        int cf[N]; // 差分数组
        void Sakuya()
        {
            FK(vis); FK(t);
            F(i,1,n) F(j,1,n)
            {
                node cur=(node){i,j,1};
                int tpos=0;
                t[++tpos]=cur;
                if (j>1) for(auto x:rec[i][j-1]) t[++tpos]=((node){x.x,x.y,cheb(x.x,x.y,i,j)});
                if (i>1) for(auto x:rec[i-1][j]) t[++tpos]=((node){x.x,x.y,cheb(x.x,x.y,i,j)});
                if (i>1 and j>1) for(auto x:rec[i-1][j-1]) t[++tpos]=((node){x.x,x.y,cheb(x.x,x.y,i,j)});
                sort(t+1,t+tpos+1,cmp_d);
                int tot=0;
                mxlen[i][j]=114514;
                // 注:如果颜色一共都没有 q+1 个,那么说明随便取都可以,此时的答案是 min(i,j)
                // 为了合并两种情况省去一些讨论, 我们令 mxlen 的初始值为 INF,然后最后和 min(i,j) 取 min
                // (这个INF臭死力)
                rec[i][j].clear();
                F(ii,1,tpos)
                {
                    node x=t[ii];
                    int c=a[x.x][x.y];
                    if (!vis[c]) // 对颜色去重
                    {
                        vis[c]=1;
                        rec[i][j].p_b(x);
                        ++tot;
                        if (tot==q+1) // 取到第 q+1 个
                        {
                            mxlen[i][j]=x.d-1;
                            break;
                        }
                    }
                }
                mxlen[i][j]=min({mxlen[i][j],i,j});
    
                F(ii,1,tpos) 
                {
                    node x=t[ii];
                    vis[a[x.x][x.y]]=0;
                }
            }
            F(i,1,n) F(j,1,n)
            {
                ++cf[0]; --cf[mxlen[i][j]+1];
            }
            F(i,1,n) cf[i]+=cf[i-1];
            F(i,1,n) printf("%d
    ",cf[i]);
        }
        void IsMyWife()
        {
            Input();
            Sakuya();
        }
    }
    #undef int //long long
    int main()
    {
        Flandre_Scarlet::IsMyWife();
        getchar();
        return 0;
    }
    
  • 相关阅读:
    用于json的 .ashx 小细节
    (转)写让别人能读懂的代码
    Mvc 中ViewBag Model 查找不到解决
    Windows 2008 R2 配置 DNS 实现二级域名
    Windows Server 2008 DNS服务器安装与配置
    【iOS】swift init构造器
    【iOS】OC-UTC日期字符串格式化
    android使用sharesdk的小感
    【iOS】Swift GCD-下
    【iOS】Swift GCD-上
  • 原文地址:https://www.cnblogs.com/LightningUZ/p/14726553.html
Copyright © 2011-2022 走看看