• Since05-03(197)


    2017-05-03(197)

    ▲22:15:08 HNOI2015 接水果 整体二分+BIT  对于第K小的问题可以转化为二分,多个询问那就整体二分,把问题变为计数问题.考虑问题的转化,路径(a,b)的子路径(c,d),假如cd的lca不是端点,那么a,b一定分别在c,d的两个子树内部,用dfs区间顺序维护第一个区间,保证L[c]<=L[a]<=R[c]第二个区间用刷漆维护,这样就可以求出目前所有合法区间里,包含d的区间个数.对于cd的lca是其中一个端点的情况,c还是在一个区间[L[c],R[c]]中,但是此时b的要求是:不在 d能走向c的那个直接儿子的子树里,只要求出不合法的个数即可.

    耶我想到正解啦!!


    2017-05-04(199)

    ▲16:56:31 CF 798D - Mike and distribution  YY 对于∑Ai>sum-∑Ai  可以考虑一对一模式-> 对于选出的每个数分别对应一个没有选出的数字比自己小就可以啦.


    2017-05-05 

    ▲10:15:48 CF 798E - Mike and code of a permutation 拓扑排序 首先肯定可以由题中所给条件构出一个DAG,然后得到一个拓扑序列就是答案.这里想不到正解的一个原因是 对于拓扑排序 思维局限在用BFS队列求解这一种方式了.这种方式就要求必须构出图,这样时空复杂度都是n^2的.

    既然BFS可以求,那么DFS是不是也可以求?  dfs是栈的结构,只要我们保证把x加入最后的拓扑序列之前,所以比x小的点都已经加入了就可以.

    访问x时,得到每个确定比x小的点y然后直接访问y,把所有的y都访问过一遍后,得到的答案就是对的.但是怎么保证复杂度呢?

    由于很多的大小关系是累赘重复的,即  a<b,b<c 那么a<c这个条件是没用的,这条边是不必要连的,考虑 dfs的过程

    访问a,再访问b,b再访问c,这样就可以确定b所有访问到的点都比a小了,回到a时,如果有c比a小的条件 也不要考虑c了.

    那么这个过程如何实现?

    可以用线段树来实现找到比我小的所有点,并且可以删除已经访问过的点.这样的复杂度就是O(n*logn)

    这种做法的复杂度是n*找到一条合法边(保证端点未访问过)的复杂度.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int M=5e5+5;
    int n,A[M],B[M],res[M],tot=0,vis[M],p[M];
    struct SEG{//维护最大值,单点删除操作  
        int t[M<<2];
        int up(int a,int b){
            if(B[a]>B[b])return a;
            return b;
        }
        void build(int l,int r,int p){
            if(l==r){
                t[p]=l;
                return;
            }
            int mid=l+r>>1;
            build(l,mid,p<<1);
            build(mid+1,r,p<<1|1);
            t[p]=up(t[p<<1],t[p<<1|1]);
        }
        int qry(int L,int R,int l,int r,int p){
            if(L==l&&R==r)return t[p];
            int mid=L+R>>1;
            if(r<=mid)return qry(L,mid,l,r,p<<1);
            else if(l>mid)return qry(mid+1,R,l,r,p<<1|1);
            else return up(qry(L,mid,l,mid,p<<1),qry(mid+1,R,mid+1,r,p<<1|1));
        }
        void del(int L,int R,int x,int p){
            if(L==R){
                t[p]=0;return;
            }
            int mid=L+R>>1;
            if(x<=mid)del(L,mid,x,p<<1);
            else del(mid+1,R,x,p<<1|1);
            t[p]=up(t[p<<1],t[p<<1|1]);
        }
    }T;
    void dfs(int x){
        vis[x]=1;
        T.del(1,n,x,1);
    //    printf("st %d
    ",x);
        if(B[x]!=n+1&&!vis[B[x]])dfs(B[x]);
        if(A[x]!=1){
            while(1){
                int a=T.qry(1,n,1,A[x]-1,1);
                if(B[a]>x)dfs(a);
                else break;
            }
        }
    //    printf("en %d
    ",x);
        res[++tot]=x;
    }
    void solve(){
        int i,j,k;
        scanf("%d",&n);
        for(i=1;i<=n;i++){
            scanf("%d",&A[i]);
            if(A[i]==-1)A[i]=n+1;
            else B[A[i]]=i;
        }
        for(i=1;i<=n;i++)if(!B[i])B[i]=n+1;
        T.build(1,n,1);
        for(i=1;i<=n;i++){
            if(!vis[i])dfs(i);
        }
        for(i=1;i<=n;i++)p[res[i]]=i;
        for(i=1;i<=n;i++){
            printf("%d%c",p[i]," 
    "[i==n]);
        }
    }
    int main(){
    //    freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code

     ▲今天做了一道卡精度辣鸡题 好恶心啊啊啊啊


    2017-05-06

    ▲11:20:21 CF793E 结论题 根据结论 把问题转化成美妙的背包可行性问题,用BITSET优化即可!!

    ▲20:14:14 CF804D 暴力出奇迹 虽然不会证明复杂度 但是还是很可做的 思路也不难想.对于 询问容易出现重复时,用map记录重复询问.


    2017-05-07

    ▲CodeForces - 768E 博弈 nim取石子的变形:

    对于基本的取石子游戏,把每堆石子的数量相异或就是答案.对于变种,要求石子对应数量的sg值,因为每堆石子是互不影响的,所以答案就是每一堆的sg值的异或和.

    现在问题就是求sg值.

    确定sg函数的定义:sg[x]为x的后继状态的sg中没有出现的最小的非负整数.因为si的范围较小,我们可以通过状压dp+计划搜索map来求解.

    暴力的做法是直接做,虽然不能在指定时间求出sg,但是可以打表!!

    优化的做法:对于dp[i][msk],msk记录当前不能选择拿走哪些数量的石子,显然对于大小为i的石子堆,最多拿走i个,因此msk中>i并且有1的位是不必要的,因此把状态压缩,每次msk&((1<<i)-1)即可.

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #include<algorithm>
     5 #include<ctime>
     6 #include<cstdlib>
     7 #include<cmath>
     8 #include<string>
     9 #include<vector>
    10 #include<map>
    11 #include<queue>
    12 #include<bitset>
    13 #define ll long long
    14 #define debug(x) cout<<#x<<"  "<<x<<endl;
    15 #define db(x,y)cout<<#x<<" "<<#y<<" "<<x<<" "<<y<<endl;
    16 using namespace std;
    17 inline void rd(int &res){
    18     res=0;char c;
    19     while(c=getchar(),c<48);
    20     do res=(res<<1)+(res<<3)+(c^48);
    21     while(c=getchar(),c>=48);
    22 }void print(int x){
    23     if(!x)return ;
    24     print(x/10);
    25     putchar((x%10)^48);
    26 }void sc(int x){
    27     if(x<0){x=-x;putchar('-');}
    28     print(x);
    29     if(!x)putchar('0');
    30     putchar('
    ');
    31 }
    32 inline void Max(int &x,int y){if(x<y)x=y;}
    33 inline void Min(int &x,int y){if(x>y)x=y;}
    34 #define mkp(a,b) make_pair(a,b)
    35 typedef pair<int,ll> pil;
    36 map<pil,int>mp;
    37 const int M=65;
    38 int sg[M],n;
    39 int SG(int a,ll msk){//记录已经用了哪些 
    40 //    for(i=a;i<h;i++)if(msk&(1<<i))msk^=(1<<i);
    41     msk=msk&((1ll<<a)-1);//[1,a] ->[0,a-1]  
    42     pil pr=mkp(a,msk);
    43     if(mp.find(pr)!=mp.end())return mp[pr];
    44     if(!a)return mp[pr]=0;
    45     
    46     int i,res=0,vis[M];
    47     for(i=0;i<=60;i++)vis[i]=0;
    48     for(i=0;i<a;i++){//现在msk里面最多是a-1 
    49         if(!(msk&(1<<i))){
    50             vis[SG(a-i-1,msk|(1<<i))]=1;
    51         }
    52     }
    53     for(i=0;i<=60;i++){
    54         if(!vis[i]){res=mp[pr]=i;break;}
    55     }
    56     return res;
    57 }
    58 int main(){
    59 //    freopen("da.in","r",stdin);
    60 //    freopen("my.out","w",stdout);
    61     int a,res=0,i,n;
    62     for(i=1;i<=60;i++)sg[i]=SG(i,0);
    63     rd(n);
    64     for(i=1;i<=n;i++){
    65         rd(a);res=res^sg[a];
    66     }
    67     if(res)puts("NO");
    68     else puts("YES");// 
    69     return 0;
    70 }
    求sg

    ▲CodeForces - 768G DSU on tree 终态分析+DSU on tree!! 每次只要维护当前子树的信息即可.

    【为什么我会把代码写得如此丑陋T_T】


    2017-05-08

    ▲CodeForces - 771D DP 逆序对!!

    对于相邻交换:

    ①类别相同的点的相对顺序是不可能改变的.

    ②交换的次数=逆序对对数.

    确定了这个以后就可以定下dp状态 我们按照结果串的顺序确定每一个字母pos,那么只要确定之前已经选择了哪些字母放在结果串,那么[1,pos-1]中没有选择个数就是逆序对的个数.我们直接记录三种字符分别的个数即可,为了满足没有vk,只要再记录最后一个的字符,就可以满足了.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<ctime>
    #include<cstdlib>
    #include<cmath>
    #include<string>
    #include<vector>
    #include<map>
    #include<queue>
    #include<bitset>
    #define ll long long
    #define debug(x) cout<<#x<<"  "<<x<<endl;
    #define db(x,y)cout<<#x<<" "<<#y<<" "<<x<<" "<<y<<endl;
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }void print(int x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }void sc(int x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    inline void Max(int &x,int y){if(x<y)x=y;}
    inline void Min(int &x,int y){if(x>y)x=y;}
    const int M=80,oo=1e9;
    int dp[M][M][M][3];//0,1,2分别表示A,v,k
    int n,A[M],B[M],C[M],a=0,b=0,c=0;
    char s[M]; 
    int cost(int i,int j,int k,int pos){//在我前面没被选  /  
        //当前前面有 pos-1  去掉备选的就是剩下的
        int t,res=pos-1;
        for(t=1;t<=i;t++)if(A[t]<pos)res--;
        for(t=1;t<=j;t++)if(B[t]<pos)res--;
        for(t=1;t<=k;t++)if(C[t]<pos)res--; 
        return res;
    }
    int main(){
    //    freopen("da.in","r",stdin);
    //    freopen(".out","w",stdout);
        int i,j,k,ans=oo,t;
        rd(n);
        scanf("%s",s+1);
        //dp[i][j][k][t]表示当前的串是i个A,j个v,k个K  最后一个是[t    ]的最小代价
        //转移: 枚举下一个是 哪一个 dp[i][j][k][t]-> 假如t是1 那就不能加k 否则找到下一个坐标算出它前面有多少个还没被选中 
        for(i=1;i<=n;i++){
            if(s[i]=='V')B[++b]=i;
            else if(s[i]=='K')C[++c]=i;
            else A[++a]=i;
        }
        for(i=0;i<=a;i++)
            for(j=0;j<=b;j++)
                for(k=0;k<=c;k++)
                    for(t=0;t<3;t++)dp[i][j][k][t]=oo;
        dp[0][0][0][0]=0;
        dp[0][0][0][2]=0;
        dp[0][0][0][1]=0;
        for(i=0;i<=a;i++){
            for(j=0;j<=b;j++){
                for(k=0;k<=c;k++){
                    if(dp[i][j][k][0]<oo){// 下一个是什么都可以 
                        if(i<a)Min(dp[i+1][j][k][0],dp[i][j][k][0]+cost(i,j,k,A[i+1]));
                        if(j<b)Min(dp[i][j+1][k][1],dp[i][j][k][0]+cost(i,j,k,B[j+1]));
                        if(k<c)Min(dp[i][j][k+1][2],dp[i][j][k][0]+cost(i,j,k,C[k+1]));
                    }
                    if(dp[i][j][k][2]<oo){// 下一个是什么都可以 
                        if(i<a)Min(dp[i+1][j][k][0],dp[i][j][k][2]+cost(i,j,k,A[i+1]));
                        if(j<b)Min(dp[i][j+1][k][1],dp[i][j][k][2]+cost(i,j,k,B[j+1]));
                        if(k<c)Min(dp[i][j][k+1][2],dp[i][j][k][2]+cost(i,j,k,C[k+1]));
                    }
                    if(dp[i][j][k][1]<oo){// 下一个不能是k 
                        if(i<a)Min(dp[i+1][j][k][0],dp[i][j][k][1]+cost(i,j,k,A[i+1]));
                        if(j<b)Min(dp[i][j+1][k][1],dp[i][j][k][1]+cost(i,j,k,B[j+1]));
                    }
                }
            }
        }
        Min(ans,dp[a][b][c][0]);
        Min(ans,dp[a][b][c][1]);
        Min(ans,dp[a][b][c][2]);
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    2017-05-09

    ▲15:52:25 codeforces 771E  很神的DP题!!

    对于"两行",最直接的思路就是O(n^2)dp:

    dp[i][j]表示第一行前i个,第二行前j个的最多长方形数量.

    用贪心的思路进行转移,预处理出从i出发最早结束的合法长方形(每一行,两行)

    可以直接前推或者后查,O(1)转移,O(n^2)状态.

    但其实这样的状态定义有很多冗余:

    假如两行是独立的,没有高度为2的长方形,那么可以直接把DP状态转为一维,对于两行分别考虑,复杂度是O(n).

    但是由于考虑一起作为一个长方形,要把dp状态同时记录下来,也就是我们用二维记录dp状态的原因是要考虑联合长方形.

    现在假如上下的i,j相差很大(i<j),以至于pre[j]>i,显然是不必要的->那就是i,j之间不能相差一个长方形.

    这样能保证合法的状态是O(n)级别.这样用计划搜索+map即可O(nlogn)求解.

    具体的转移  就是 只向前转移pre[i][0]和pre[j][1]较大者.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<map>
    #define ll long long
    using namespace std;
    #define mkp(a,b)  make_pair(a,b)
    typedef pair<int,int> pii;
    const int M=3e5+5;
    ll sum[3][M];//三个sum
    int n,pre[3][M],F[M];
    map<ll,int>mp;
    map<pii,int>f;
    int DP(int x,int y){
        pii p=mkp(x,y);
    
        if(f.find(p)!=f.end())return f[p];
    //    if(!x||!y)return f[p]=-1;    
         
        int res=F[min(x,y)]; 
        if(pre[0][x]>pre[1][y])res=max(res,DP(pre[0][x],y)+1);
        else if(pre[1][y])res=max(res,DP(x,pre[1][y])+1);
        //这样可以保证 每一次的x,y都不超过1个 
        return f[p]=res;
    }
    int main(){
    //    freopen("da.in","r",stdin);
    //    freopen(".out","w",stdout);
        scanf("%d",&n);n++;
        int i,k,a;
        for(k=0;k<2;k++){
            mp.clear();mp[0]=1;
    
            for(i=2;i<=n;i++){
                scanf("%d",&a);
                sum[k][i]=sum[k][i-1]+a;
                pre[k][i]=max(pre[k][i-1],mp[sum[k][i]]);
                mp[sum[k][i]]=i;
            }
        }
        mp.clear();mp[0]=1;
        for(i=2;i<=n;i++){
            sum[2][i]=sum[1][i]+sum[0][i];
            pre[2][i]=max(pre[2][i-1],mp[sum[2][i]]);
            mp[sum[2][i]]=i;
    //        for(k=0;k<3;k++)printf("%d %d %d
    ",i,k,pre[k][i]);
        }
        F[1]=0;
        F[0]=-1;
        f[mkp(1,1)]=0;
        f[mkp(0,0)]=-1;
        for(i=2;i<=n;i++){
            F[i]=max(F[pre[2][i]]+1,DP(i,i));
            f[mkp(i,i)]=F[i];
        }
        printf("%d
    ",F[n]);
        return 0;    
    }
    View Code

    对于双塔DP(?)或者别的什么需要几个东西相互联系的DP,要找到关联的地方,不关联的地方可以各自求解.


    2017-05-17

    COCI2016/2017 contest#4

    F:

    首先 总共的方案数并不多,只有n^2*8种可能-> 只有400w

    首先字符串相等问题
    1)匹配算法
    2)hash

    因为这里需要 对一个串反复叠加 所以hash是最合适的选择

    现在问题就转化成了:在一定时间内求出每种可能得到的hash值

    这里非常非常关键的一点是:

    从a,b出发,根据某个方向走j步的位置是a+j*dx,b+j*dy

    把坐标分别对n,m取模就可以了,那就可以直接定位了.

    如果K较小 可以直接暴力走每一步 得到整个串的hash值

    但是现在K好大啊~可以用倍增啊~

    可以处理出走一步的hash值

    然后再处理二的幂次的hash值就可以了.【就是所谓的“一”生万物】

    我一开始想复杂了,就是考虑周期啊,余数啊什么乱七八糟 到头来还是需要倍增,而且代码写得也麻烦.

    这里有一些卡常的奇技淫巧:

    1)hash  用一个素数容易冲突,所以考虑用两个基底,再用unsigned int!!!快了一倍.

    2)能够预处理的信息 就不要在for中计算了.

    3)尽量少调用mkp!!别看它写起来方便,其实很慢啊!!!所以以下的写法是最好的

    1 pii operator*(pii a,pii b){
    2     a.fi*=b.fi;a.se*=b.se;
    3     return a;
    4 }
    5 pii operator+(pii a,pii b){
    6     a.fi+=b.fi;a.se+=b.se;
    7     return a;
    8 }
    View Code

    E/HDU3460:

    对于很多串的LCS/LCP可以考虑建立trie树
    这样就把问题转化为树形结构.

    D:

    小数转整数:

    我们把初始的n个数字,分为两类:

    1)在区间中只出现一次.

    2)在区间中出现了两次及以上.

    对于第二种情况,可以确定这样的数字x一定是给出的集合中的某个两数字的差.

    因为n不大,所以可以暴力枚举所有可能的x.注意这里还要保证Ai是x的倍数才行.

    相当于有公差为x的等差数列.

    找到所有合法的等差数列,并且把本质相同的合并.

    现在问题就转化成了:有一些集合,每个集合有一些数字,现在找到最少的集合,使所选的数字包含所有数字.

    这是个经典的set-cover问题,是NPC的.但是我们可以用贪心或者启发式算法求解.

    我就是根据集合大小排序,每次选出集合中没被选择的数字最多的集合选中它.

    数据是不强的,因此稍微靠谱一点的贪心都可以过.

    这道题的关键就是解的范围,最后n个数字的选择是有限的.

    C:

    看到题目显然是DP.

    最暴力的dp是很好想的:

    dp[i][j][k]表示前i个数字,给第一个人j,第二个人k是否可以.可以是1,不可以是0.

    复杂度是n*sum*sum,这个显然是不行的.

    对于当前的dp,它的值只有0,1,这样不能够合理地运用信息,状态压缩.

    考虑可以把某个信息用dp值来记录.

    再回到问题,问题只要保证两者得到的值相等,并且两者得到的值尽可能大,那么直接用差值来作为状态定义.

    dp值就存储前者的值即可.

    每个张钞票有三种结果,分别考虑,O(1)转移.复杂度就是n*SUM.

    遇到DP题,考虑充分利用每个信息来设计状态.包括下标和dp值.

    COCI2016/2017 contest#3

    E:

    最重要的一点是进行终态分析:

    题目规定的这种奇奇怪怪规则,最后一定可以化简成较简单直接的信息.比如此题:虽然规则是每次一定要往两边放,但是我们可以发现,最终的LIS被一个数字分割成两个部分,两个部分分别都是LIS.那么就把问题简化了.

    1)问题就变为求最小值为i的LIS及其方案数.

    2)对于LIS问题,很容易想到用线段树或者BIT优化一波.

    3)对于方案数的求解,要考虑所有的情况,包括特殊情况(n=1,lis=1,只在1的某一边等等情况)

    D:

    1)对于表达式,一般都用栈结构来处理.

    2)

    对于加法表达式,直接选取每个最值,再和上限取min.

    对于乘法表达式,可以用差量法,发现,最后一定让最大值最小.

    也就是对于这个问题:

    现在有Xi<=Ai,∑Xi<=sum,使得Xi的连乘最大.

    将每个Xi取Ai,如果和超过sum,再从大到小将每个Xi变为次小值...以此类推直到sum符合条件.

    那么就可以直接贪心求解了.

    C:

    数据范围特别小->状压DP搞一波!!!!


    2017-05-18

    COCI 2015/2016 Contest#5 F 

    AC自动机/后缀自动机:

    AC自动机的fail数组的性质:对于节点x,所有x的可能后缀单词一定会在fail路径中出现.

    可以通过fail数组的迭代得到后缀的信息.

    用fail当做fa建立的树,可以看做的是后缀的树.

    那么此题就可以解决了:

    每个给出一个串,得到每个位置匹配到的节点x,则x到fail树上的根的一段路径都可以匹配到.

    但是有可能一个单词重复出现,因此只要在LCA处-1即可保证直接区间求和不重复.


    2017-05-19

    WC2013 糖果公园 树上莫队带修改.

    树上莫队: 构建dfs序,分两类把一段路径转化成一个区间-> 序列莫队.

    序列莫队带修改-> 根据l/sz,r/sz,t排序,暴力修改,此时sz设置为n^(2/3). 这样最后的复杂度为n^(2/3)*m

    树上莫队带修改只要把以上两点相结合即可.

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<iostream>
    #define ll long long
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(ll x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(ll x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    const int M=1e5+5;
    const int S=18;
    int n,m,q,tot=0,qr=0,C[M],v[M],w[M];//tot表示修改的总数 
    int pre[M],px[M],py[M];//q表示修改操作的信息 
    int ec=2,head[M],nxt[M<<1],to[M<<1];
    int dep[M],st[M],en[M],clo=0,fa[M][S],mp[1<<S],pt[M<<1];
    int L=1,R=0,vis[M],cnt[M],sz;//cnt记录第i种权值的个数 
    ll res[M],now=0;
    struct node{
        int a,b,t,id,lca;//分别表示左右端点所在的块id,在该询问前的操作个数,和lca 
        bool operator<(const node &tmp)const{
            int a1=a/sz,a2=tmp.a/sz;
            if(a1!=a2)return a1<a2;
            a1=b/sz,a2=tmp.b/sz;
            if(a1!=a2)return a1<a2;
            return t<tmp.t;//根据前面经历的修改次数从小到大排序 
        }
    }A[M];
    void ins(int a,int b){//双向边 
        to[ec]=b;nxt[ec]=head[a];head[a]=ec++;
        to[ec]=a;nxt[ec]=head[b];head[b]=ec++;
    }
    void dfs(int x,int f){
        st[x]=++clo;
        pt[clo]=x;
        fa[x][0]=f;
        for(int i=head[x];i;i=nxt[i]){
            if(to[i]!=f){
                dep[to[i]]=dep[x]+1;
                dfs(to[i],x);
            }
        }
        en[x]=++clo;
        pt[clo]=x;
    }
    int LCA(int a,int b){
        int k,step=dep[a]-dep[b],i;
        while(step){
            k=step&-step;
            a=fa[a][mp[k]];
            step-=k;
        }
        if(a==b)return a;
        for(i=S-1;i>=0;i--)
            if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
        return fa[a][0];
    }
    void mdfy(int t,bool f){//进行第t个修改 
        int x=px[t],l=st[x],r=en[x],y=py[t];
        if(!f)y=pre[t];
        if((l<=R&&l>=L)^(r<=R&&r>=L)){///恰好有一个在[L,R]中 
            now-=1ll*v[C[x]]*w[cnt[C[x]]];
            cnt[C[x]]--;
            cnt[y]++;
            now+=1ll*v[y]*w[cnt[y]];
        }
        C[x]=y;    
    }
    void upd(int x){ //f=0 表示删除 
        x=pt[x];
        if(vis[x]){//
            now-=1ll*v[C[x]]*w[cnt[C[x]]];
            cnt[C[x]]--;
            vis[x]=0;    
        }
        else{
            cnt[C[x]]++;
            now+=1ll*v[C[x]]*w[cnt[C[x]]];
            vis[x]=1;
        }
    }
    int main(){
    //    freopen("da.in","r",stdin);
    //    freopen("da.in","r",stdin);
    //    freopen("my.out","w",stdout);
        rd(n);rd(m);rd(q);
        int i,j,a,b,op,c,t;
        sz=(int)pow(n,2.0/3);//n的2/3次 三次根号的平方.... 
    
        for(i=0;i<S;i++)mp[1<<i]=i;
        for(i=1;i<=m;i++)rd(v[i]);
        for(i=1;i<=n;i++)rd(w[i]);
        for(i=1;i<n;i++){
            rd(a),rd(b);
            ins(a,b);
        }
        for(i=1;i<=n;i++)rd(C[i]);
        
        dfs(1,1);//得到dfs序,dep,fa 
        
        for(j=1;j<S;j++)
            for(i=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1];
    
        for(i=1;i<=q;i++){
            rd(op);rd(a),rd(b);
            if(!op){
                pre[++tot]=C[a];
                C[a]=b;
                px[tot]=a,py[tot]=b;
            }
            else{
                if(dep[a]<dep[b])swap(a,b);
                c=LCA(a,b);
                if(c==b)A[++qr]=(node){en[a],en[b],tot,qr,c};//有可能a,b相等 不用考虑特殊情况 
                else {
                    if(st[a]>st[b])swap(a,b);
                    A[++qr]=(node){st[a]+1,en[b]-1,tot,qr,c};//l<=r 
                }
            }
        }
        sort(A+1,A+1+qr);
        for(t=tot,i=1;i<=qr;i++){
            while(A[i].t>t)mdfy(++t,1);
            while(A[i].t<t)mdfy(t--,0);
            while(A[i].b>R)upd(++R);
            while(A[i].a<L)upd(--L);
            while(A[i].b<R)upd(R--);
            while(A[i].a>L)upd(L++);
            res[A[i].id]=now;
            if(A[i].lca!=pt[A[i].b])
                res[A[i].id]+=1ll*v[C[A[i].lca]]*w[cnt[C[A[i].lca]]+1];
        }
        for(i=1;i<=qr;i++)sc(res[i]);
        return 0;
    }
    View Code

    2017-05-24

    COCI2012/2014 F  得到dp方程+发现可以斜率优化+斜率优化来一波.

    另一种方法是线性规划 其实差不多,都是维护凸包.复杂度nlogn.

    注意注意】把整数用除法化成小数时,要写成: p=(db)a/b    或者p=1.0*a/b  不能写成: (db)(a/b)!!!!


    2017-05-25

    啊啊啊啊博客园原来是有latex的!我是傻逼!!!

    codeforces678F  分块/线段树/斜率优化

    这里是萌萌哒的题解


    假如已经确定了当前有哪些点(x,y)是存在的,那么可以把点根据x维排序,维护一个凸包进行求解.

    那么暴力的做法就是每次对点集进行更新(删除或者加入)都重新排序(可以用插入排序,O(n)修改)、重建凸包.遇到询问再二分查找.
    复杂度是O(n^2).

    这个解法显然是不够优秀的.它的复杂度集中在重构凸包上.每次修改之后都重构是不必要的,我们可以考虑把所有操作分块来解决.

    处理第i块(假设块的区间为[l,r])的询问,把所有在[l,r]整个区间都存活着的点找到然后构造一个凸包.
    块里的每个询问直接在凸包上二分找到最优值.
    当然这个最优值并不一定是答案.因为有可能最优值并不在整个[l,r]中出现,可能在l之后出现,或者在r之前就被删除了.但是这些点对于块中的询问i来说可能是合法的选择,因此还要对块里的每个元素进行特判更新答案.

    如果把块的大小设置为sqrt{n},总的复杂度就是对于每个块构造一次凸包(只要在一开始把所有的点按照x维排序,构造凸包就是O(n)的)的nsqrt{n},以及对于每个询问查询最优值和暴力更新块里每个元素的复杂度n(sqrt{n}+logn)

    总复杂度为n(sqrt{n}+logn)


    还有一种做法:

    还是处理在[l,r]中的询问,还是找到所有在整个区间[l,r]都存活着的点,然后建立凸包,更新答案.
    同样的也需要解决一个问题,还有一些合法的点没有被更新到.那么我们可以递归进行处理.
    把区间分为[l,mid][mid+1,r]两个部分
    分别进行同样的步骤,但注意,已经在[l,r]进行更新的点,就不必在[l,mid][mid+1,r]中再次更新了.

    现在又有个问题,怎么确定每个点去更新哪些区间?

    我们发现当前我们处理问题的结构类似于线段树的结构.每个点都有个存在的时间区间[st,en],它能够更新的区间就是所有能够被[st,en]包含,并且它的父亲区间不能被包含的区间.

    假设n=10,那么区间的结构如下所示,现在有一个点出现的时间区间为[4,10]
    那么它就会更新[4,5][6,10]这两个区间.

    [1,10]
    [1,5] [6,10]
    [1,3] [4,5] [6,8] [9,10]
    [1,2][3,3][4,4][5,5][6,7][8,8][9,9][10,10]
    .........................

    这就类似于线段树上的区间更新操作所更新到的区间.
    可以保证每个询问最多更新logn个区间.
    对于每个区间[l,r],就可以确定刚好在[l,r]整个出现的点集.
    再排序,构造凸包,更新答案.
    复杂度为O(nlogn^2) 

    对于有修改(添加,删除)这两种方法是蛮经典的解法,以后遇到这类题目可以往这个方向考虑.

  • 相关阅读:
    Andriod一段时间未操作页面,系统自动登出
    Error:Execution failed for task ':app:clean'
    Handler的postDelayed(Runnable, long)
    Android Studio快捷键大全
    Cookie、Session、Token的区别
    CentOS 7 上安装jdk
    CentOS 7 上搭建nginx来部署静态网页
    PyCharm如何设置 “ctrl+滚轮” 实现字体的放大和缩小
    HTTP和HTTPS
    性能测试思想(What is performance testing?)
  • 原文地址:https://www.cnblogs.com/Lay-with-me/p/6804321.html
Copyright © 2020-2023  润新知