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\n",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\n",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]," \n"[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('\n');
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('\n');
}
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\n",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\n",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\n",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('\n');
}
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)的)的n\sqrt{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) 

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

posted @ 2017-05-03 22:21  LIN452  阅读(31)  评论(0编辑  收藏  举报