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; }
▲今天做了一道卡精度辣鸡题 好恶心啊啊啊啊
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 }
▲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; }
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; }
对于双塔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 }
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; }
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)
对于有修改(添加,删除)这两种方法是蛮经典的解法,以后遇到这类题目可以往这个方向考虑.