不知道写什么题了,那就来刷东方题单吧!
绿以下的题被以前的我秒完了,所以从绿开始写。
大概只会做简单题。
题目按照我做的顺序排列。
题单
被 *1700 的诈骗创飞了。
思考的时候一直在从字符串匹配的角度出发,然后啥也想不出来。
想贪心也贪不动,因为保证唯一解所以没东西能贪。
中间想到过一点正确思路:起始的那个字母在后面没有出现过。
但是瞬间弃掉了。
事实上,我们只需要关心每个字母的出现次数即可。
开始的那个字母在所有的字符串中一定出现了奇数次,其他字母一定都出现了偶数次。
证明也是容易的:
把最终字符串当成一次操作,删除整串,并添加空串,则所有字母出现的次数都是 0(偶数)。
开始时只有一个字母出现次数为奇数,即中间的改变次数为奇数,其他字母的改变次数为偶数。
即开始的那个字母在所有的字符串中一定出现了奇数次,其他字母一定都出现了偶数次。
单调队列优化 DP 模板题。
强连通分量模板题。
疑似学高级数据结构学傻了,树套树做不了就没思路了。
十字取反,矩形求和,听上去是不是非常不可做?
事实上,我们只需要对行和列分别维护一颗线段树即可。
因为一个点的状态只和它所在的行和列的操作次数的奇偶性有关:若行和列的操作次数的奇偶性相同,则这个点为 0,否则为 1。
所以只需要分别维护每行和每列的操作次数的奇偶性即可。
此时十字取反变成了单点取反,矩形求和变成了区间求和。
做完了。
我是期望低手,让我做期望不如沙了我。
首先加强一下,把 7 种改成 种。
设 。
根据期望的线性性,我们可以用所有方案的总贡献除以方案数得到期望。
方案数是好算的,就是一个多重集的排列数。
然后考虑贡献。
我们考虑每个长度为 的区间带来的贡献。显然每个区间都是相同的,所以只需要考虑一个然后乘上区间数量即可。
因为每个长度为 的区间贡献都是 1,所以依旧算方案数即可。
因为要这个保证这个区间是合法的,所以必须每种元素有一个,方案数为 。
剩下位置可以乱填,又是一个多重集的排列数。
最后乘上区间数量即可。
所以答案为
化简一下,可以得到
下划线代表下降幂。
1.考虑最小割。
建一个汇点,把所有叶子向汇点连边,然后直接求最小割即可。
为什么这么做就是对的?
因为一棵树中,任意两点间有且只有一条简单路径,所以只需要把根到叶子的路径上的任意一条边割掉即可,所以求最小割就是对的。
2.考虑 DP。
加强版:[SDOI2011] 消耗战
设 表示根和以 为根子树不连通的最小代价, 表示根到 路径上的最小边权,转移为
复杂度
区间加等差数列,常见做法是维护差分,这样就变成对差分数组区间加了。
但是这道题复杂度要求 ,不能直接线段树。
不过没有一边修改一边查询,而是只有最后有一次查询,所以区间加可以差分一下变成单点加。
因为做了两次差分,所以最后做两遍前缀和即可复原。
考验选手观察能力。
如果把题目中给的图继续往下写几行,就可以发现:序列的长度是斐波那契数列,1 的个数也是斐波那契数列。
然后查询只需要差分一下即可。
那题目里给的初始状态呢?没有用吗?
是的。
这是因为无论初始状态是什么样,在经过无限长的时间后,序列都和一开始只有一个 1 的状态是一样的。
查询时的拆分为什么正确?
因为根据齐肯多夫定理:任意正整数可以被拆分为若干个不连续的 Fibonacci 数之和。
这个东西在 [SNOI2020] 取石子 中也有使用(斐波那契进制下的数位 DP)。
分层图最短路模板题。
分成 层,每向下走一层代表用一张卡,最后对每一层的终点距离取 即可。
刚学搜索的时候做的题,现在怎么升蓝了。
写一个堆优化的 BFS 就可以过了。
没啥思维量,就是代码比较难写,细节比较多。
又 是 期 望。
依旧根据期望的线性性,先算总贡献,再算方案数。
我们把题目中的游戏转化一下,从另一个角度考虑。
我们把题目中的向右走和停下拆开,拆成每次只能向右走一步和在当前格停下。
可以发现,我们只有在停下时才会有收益。
设 表示前 格停下了 次的方案数, 表示前 格停下了 次的总收益,转移为
转移容易理解,就是从上一格走过来或者在当前格停下,停下时可以获得收益。
那么为什么这种理解方式和之前那种游戏的操作是对应的?
事实上,我们只是把之前的向右移动若干步并停下拆成了向右移动一步和停下。
而且我们的每一次停下都对应原来的一次操作,这就一一对应了。
答案为 。
需要滚动数组。
啥玩意,这是来搞笑的吗。
预处理组合数,然后求前缀和,没了啊?
一开始还以为读错题了,这能有绿啊?
第一眼以为是高维前缀和,然后不会了。
什么时候才能记住位运算的每一位是独立的!!!
既然是异或,那我们按位考虑。
假设当前考虑到第 位,那我们把所有数的第 位拿出来。现在就是要求这个 01 序列的所有子集的异或和的和。
如果整个序列都是 0,那么答案显然是 0。
如果有至少一个 1,那么我们可以特殊的考虑其中的一个 1。
剩下的 个元素一共有 种选法。而无论哪种选法,我们都可以控制这个特殊的 1 选不选来让这种选法产生贡献。
所以一共会产生 的贡献。
最后把这些贡献加起来就是对的。
事实上,我们只需要把所有数都或起来,然后乘 就是答案(想一想为什么)。
学到了没用的新技巧:如何开桶。
第一想法是开桶记录每种数出现个数。
但是值域让我们无法做到这一点。
离散化也不行,我们做不到存下来这些数。
但有一个性质:一个数的出现次数和这个数在另一个进制下每一位上的数字的出现次数相同。
这很好理解,就是把这个数换了个进制表示而已。
这非常好:因为我们能开的下桶了。
这里我选了 100 进制,已经足够用了()。
以下是一份没有加快读的代码,无法通过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=10 ,base=100 ;int n,k;int buc[maxn][base];inline int qpow (int a,int b) { int res=1 ; while (b){ if (b&1 ) res=res*a; b>>=1 ; a=a*a; } return res; }inline void add (int x) { int len=0 ; while (x){++buc[len++][x%base],x/=base;} }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>k; for (re int i=1 ,x;i<=n;++i) cin>>x,add (x); int ans=0 ; for (re int i=0 ;i<=9 ;++i){ for (re int j=0 ;j<=99 ;++j){ buc[i][j]%=k; if (!buc[i][j]) continue ; ans+=qpow (base,i)*j; } } cout<<ans; return 0 ; }
好吧,又想少了。
可以发现,。
所以直接算就行了?
显然不对,如果 就寄了。
但题目保证了存在 ,所以找出来相邻的两个不同的数算出来 然后往两边推出来所有 即可。
这不是我们 CSP-S 2024 一轮的 序列合并 吗?
容易想到二分答案,然后转化成数数问题。
设当前二分到 ,则需要数 的点对 的对数。
移项,得到 。
设 。
那么现在问题转化成了求 的点对 的对数。
这是经典问题,排序后双指针即可(也可以树状数组,感觉更好写,不过这题是浮点数用不了树状数组)。
大模拟。
所以我写了这道题。
总用时约 1.5 h。
一些注意事项:
变量名别写错。
不知道为什么 stoi 寄了,所以需要手写 string 转 int。
除法向上取整在负数情况下会寄,需要用 floor。
double 状态的人在出完第一张基本牌时要把 double 状态清掉。
仔细读题,别漏细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=35 ,maxm=3e5 +10 ,K=99 ,inf=1e18 ;int n,m,k;int turn,now,nowp,nowpai;inline int to_int (string s) { int res=0 ; for (re int i=0 ;i<s.length ();++i) res=res*10 +(s[i]-'0' ); return res; }struct card { string name; int op,x; inline void init () { if (name=="PASS" ) op=6 ; else if (name=="TURN" ) op=7 ; else if (name=="DOUBLE" ) op=8 ; else { string s="" ; for (re int i=1 ;i<name.length ();++i) s+=name[i]; x=to_int (s); if (name[0 ]=='A' ) op=1 ; if (name[0 ]=='B' ) op=2 ; if (name[0 ]=='C' ) op=3 ; if (name[0 ]=='D' ) op=4 ; if (name[0 ]=='E' ) op=5 ; } } }t[maxm];struct player { string name; card c[4 ]; bool op; }p[maxn];inline void end_turn (int id) { p[now].c[id]=t[nowpai++]; now=(now+turn)%n;if (!now) now=n; }inline bool solve2 () { int nxtp=0 ,mxp=-inf,id=0 ,cnt=0 ; for (re int j=1 ;j<=3 ;++j){ if (p[now].c[j].op<=5 ){ ++cnt; if (p[now].c[j].op==1 ){ nxtp=nowp+p[now].c[j].x; if (nxtp<=K){ if (mxp<nxtp) mxp=nxtp,id=j; else if (mxp==nxtp){ if (p[now].c[id].op==2 ||p[now].c[id].op==4 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==2 ){ nxtp=nowp-p[now].c[j].x; if (nxtp<=K){ if (mxp<nxtp) mxp=nxtp,id=j; else if (mxp==nxtp){ if (p[now].c[id].op==4 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==3 ){ nxtp=nowp*p[now].c[j].x; if (nxtp<=K){ if (mxp<nxtp) mxp=nxtp,id=j; else if (mxp==nxtp){ if (p[now].c[id].op==1 ||p[now].c[id].op==2 ||p[now].c[id].op==4 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==4 ){ nxtp=floor (1.0 *nowp/p[now].c[j].x); if (nxtp<=K){ if (mxp<nxtp) mxp=nxtp,id=j; else if (mxp==nxtp){ if (p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==5 ){ nxtp=p[now].c[j].x; if (nxtp<=K){ if (mxp<nxtp) mxp=nxtp,id=j; else if (mxp==nxtp){} } } } } if (mxp==-inf){ if (cnt==3 ){ for (re int j=1 ;j<=3 ;++j) p[now].c[j]=t[nowpai++]; cout<<p[now].name<<" lost the game.\n" ; return 0 ; } else { int id1=0 ,id2=0 ,id3=0 ; for (re int j=1 ;j<=3 ;++j){ if (p[now].c[j].op==6 ) id1=j; if (p[now].c[j].op==7 ) id2=j; if (p[now].c[j].op==8 ) id3=j; } if (id1){ cout<<p[now].name<<" used " <<p[now].c[id1].name<<",now p=" <<nowp<<".\n" ; end_turn (id1); } else if (id2){ turn*=-1 ; cout<<p[now].name<<" used " <<p[now].c[id2].name<<",now p=" <<nowp<<".\n" ; end_turn (id2); } else if (id3){ int nxt=(now+turn)%n; if (!nxt) nxt=n; p[nxt].op=1 ; cout<<p[now].name<<" used " <<p[now].c[id3].name<<",now p=" <<nowp<<".\n" ; end_turn (id3); } } } else { nowp=mxp; cout<<p[now].name<<" used " <<p[now].c[id].name<<",now p=" <<nowp<<".\n" ; end_turn (id); } return 1 ; }inline bool solve1 () { int id1=0 ,id2=0 ,id3=0 ; for (re int j=1 ;j<=3 ;++j){ if (p[now].c[j].op==6 ) id1=j; if (p[now].c[j].op==7 ) id2=j; if (p[now].c[j].op==8 ) id3=j; } if (id1){ p[now].op=0 ; cout<<p[now].name<<" used " <<p[now].c[id1].name<<",now p=" <<nowp<<".\n" ; end_turn (id1); p[now].op=1 ; } else if (id2){ turn*=-1 ; p[now].op=0 ; cout<<p[now].name<<" used " <<p[now].c[id2].name<<",now p=" <<nowp<<".\n" ; end_turn (id2); p[now].op=1 ; } else if (id3){ p[now].op=0 ; cout<<p[now].name<<" used " <<p[now].c[id3].name<<",now p=" <<nowp<<".\n" ; end_turn (id3); p[now].op=1 ; } else { int nxtp=0 ,mnp=inf,id=0 ; for (re int j=1 ;j<=3 ;++j){ if (p[now].c[j].op<=5 ){ if (p[now].c[j].op==1 ){ nxtp=nowp+p[now].c[j].x; if (nxtp<=K){ if (mnp>nxtp) mnp=nxtp,id=j; else if (mnp==nxtp){ if (p[now].c[id].op==3 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==2 ){ nxtp=nowp-p[now].c[j].x; if (nxtp<=K){ if (mnp>nxtp) mnp=nxtp,id=j; else if (mnp==nxtp){ if (p[now].c[id].op==1 ||p[now].c[id].op==3 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==3 ){ nxtp=nowp*p[now].c[j].x; if (nxtp<=K){ if (mnp>nxtp) mnp=nxtp,id=j; else if (mnp==nxtp){ if (p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==4 ){ nxtp=floor (1.0 *nowp/p[now].c[j].x); if (nxtp<=K){ if (mnp>nxtp) mnp=nxtp,id=j; else if (mnp==nxtp){ if (p[now].c[id].op==2 ||p[now].c[id].op==1 ||p[now].c[id].op==3 ||p[now].c[id].op==5 ) id=j; } } } if (p[now].c[j].op==5 ){ nxtp=p[now].c[j].x; if (nxtp<=K){ if (mnp>nxtp) mnp=nxtp,id=j; else if (mnp==nxtp){} } } } } if (mnp==inf){ p[now].op=0 ; for (re int j=1 ;j<=3 ;++j) p[now].c[j]=t[nowpai++]; cout<<p[now].name<<" lost the game.\n" ; return 0 ; } else { nowp=mnp; cout<<p[now].name<<" used " <<p[now].c[id].name<<",now p=" <<nowp<<".\n" ; p[now].c[id]=t[nowpai++];p[now].op=0 ; } return solve2 (); } return 1 ; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>k; for (re int i=1 ;i<=n;++i){ cin>>p[i].name; cin>>p[i].c[1 ].name>>p[i].c[2 ].name>>p[i].c[3 ].name; p[i].c[1 ].init ();p[i].c[2 ].init ();p[i].c[3 ].init (); } for (re int i=1 ;i<=k;++i) cin>>t[i].name,t[i].init (); now=1 ;nowpai=1 ; for (re int i=1 ;i<=m;++i){ cout<<"Round " <<i<<":" <<'\n' ; nowp=0 ;turn=1 ; while (1 ){ if (p[now].op){if (!solve1 ()) break ;} else {if (!solve2 ()) break ;} } } return 0 ; }
做这题时 PTSD 了。
可以想到,先处理出每个人能走到的最左侧点和最右侧点,显然我们只需要考虑那些走不到合法点的区间。
那么此时转化成了最少点覆盖区间问题,也就是我调了三个小时的 CSP-S 2024 T2。
现在的问题是如何处理这个区间。
暴力处理是 的,但是这里有很多重叠信息可以利用。
下面只考虑右端点怎么求,左端点同理。
我们把人的位置排序,从左向右考虑。
如果之前的人能走到的最远位置小于等于当前位置,那么让这个人暴力向右拓展。
否则,我们从之前的人能走到的最远位置开始暴力向右拓展。
每个位置最多被拓展一遍,所以复杂度是 的。
这个单调性还是挺好理解的吧,而且感觉这东西和 manacher 挺像的?
希望 NOIP 别再爆了。
别再爆了别再爆了别再爆了别再爆了别再爆了别再爆了。
读完题以后第一想法是暴力建图然后变成 Minimal Labels 。
然而暴力建图会有 条边,所以死掉了。
所以考虑优化建图。
容易发现,我们会建一堆重边,只要能去掉这些重边,边数就会变成 级别。
考虑使用并查集,连边时把父亲也更新。
因为需要正着连和反着连,所以需要两个并查集。
然后没了。
小清新计数题。
但是非常容易算重。
把贡献分为两部分:自己产生的和若干串拼接产生的。
自己内部的贡献很好算,不再赘述。
在弱化版中,保证了一个串内至少包含两种不同的字母符,所以若干串拼接一定是两个串拼接。
设前一个串的后缀长度为 ,后一个串的前缀长度为 。
直接算会算重:当 或 时,会有一些部分在同一个字符串中,但这部分贡献已经被计算过了。
这是前后两个串的后缀和前缀。
第一种情况:
那么此时的合法位置数为 。
第二种情况: 或
那么此时的合法位置数为 。
综上,合法方案数应为 。
因为会贡献到 种排列中(捆绑法),所以还要乘上 。
实现时,我们不能 的枚举字符串,但 ,所以可以枚举前后缀长度。
但枚举前后缀长度时,如果一个字符串的前缀和后缀都是一个字符,可能会把它自己和自己拼接的贡献也算上,应该去掉。
这是一份 枚举字符串的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include "bits/stdc++.h" #define re register #define int long long #define pii pair<int,int> #define fi first #define se second using namespace std;const int maxn=1e6 +10 ,maxv=30 ,mod=998244353 ;int n,m,k,ans;int fac[maxn]; string s; vector<pii> g1[maxv],g2[maxv];inline void init () { fac[0 ]=1 ; for (re int i=1 ;i<=n;++i) fac[i]=fac[i-1 ]*i%mod; }inline void solve (int id) { int pre=1 ;char c1=s[0 ]; for (re int i=1 ;i<m;++i){ if (s[i]!=c1) break ; else ++pre; } g1[c1-'a' ].push_back ({id,pre}); int suf=1 ;char c2=s[m-1 ]; for (re int i=m-2 ;i>=0 ;--i){ if (s[i]!=c2) break ; else ++suf; } g2[c2-'a' ].push_back ({id,suf}); int len=0 ;char lst=0 ; for (re int i=0 ;i<m;++i){ if (s[i]!=lst){ if (len>=k) ans=(ans+(len-k+1 )*fac[n]%mod)%mod; lst=s[i],len=1 ; } else ++len; } if (len>=k) ans=(ans+(len-k+1 )*fac[n]%mod)%mod; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>k;init (); for (re int i=1 ;i<=n;++i){ cin>>s; solve (i); } for (re int i=0 ;i<26 ;++i){ for (auto x:g2[i]){ for (auto y:g1[i]){ if (x.fi==y.fi) continue ; int suf=x.se,pre=y.se; if (suf+pre>=k) ans=(ans+(min (suf+pre-k+1 ,suf)-max (suf-k+2 ,1ll )+1 )*fac[n-1 ]%mod)%mod; } } } cout<<ans<<'\n' ; return 0 ; }
这是一份 枚举前后缀长度的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include "bits/stdc++.h" #define re register #define int long long #define pii pair<int,int> #define fi first #define se second using namespace std;const int maxn=1e6 +10 ,maxm=110 ,maxv=30 ,mod=998244353 ;int n,m,k,ans;int fac[maxn];int pre1[maxm][maxv],suf1[maxm][maxv];int a[maxm][maxm][maxv]; string s;inline void init () { fac[0 ]=1 ; for (re int i=1 ;i<=n;++i) fac[i]=fac[i-1 ]*i%mod; }inline void solve (int id) { int pre=1 ;char c1=s[0 ]; for (re int i=1 ;i<m;++i){ if (s[i]!=c1) break ; else ++pre; } ++pre1[pre][c1-'a' ]; int suf=1 ;char c2=s[m-1 ]; for (re int i=m-2 ;i>=0 ;--i){ if (s[i]!=c2) break ; else ++suf; } ++suf1[suf][c2-'a' ]; if (c1==c2) ++a[pre][suf][c1-'a' ]; int len=0 ;char lst=0 ; for (re int i=0 ;i<m;++i){ if (s[i]!=lst){ if (len>=k) ans=(ans+(len-k+1 )*fac[n]%mod)%mod; lst=s[i],len=1 ; } else ++len; } if (len>=k) ans=(ans+(len-k+1 )*fac[n]%mod)%mod; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>k;init (); for (re int i=1 ;i<=n;++i){ cin>>s; solve (i); } for (re int i=0 ;i<26 ;++i){ for (re int pre=1 ;pre<=m;++pre){ for (re int suf=max (k-pre,1ll );suf<=m;++suf){ ans=(ans+(pre1[pre][i]*suf1[suf][i]-a[pre][suf][i])%mod*(min (suf+pre-k+1 ,suf)-max (suf-k+2 ,1ll )+1 )%mod*fac[n-1 ]%mod)%mod; } } } cout<<ans; return 0 ; }
上一题的加强版。
现在可能出现这种东西:一个只由一种字符构成的字符串。
那么贡献要分为五种情况:
单个串自己的贡献
若干只由一种字符构成的字符串拼接
一段后缀+若干只由一种字符构成的字符串
若干只由一种字符构成的字符串+一段前缀
一段后缀+若干只由一种字符构成的字符串+一段前缀
设 表示前缀长度为 ,中间有 个只由一种字符构成的字符串,后缀长度为 的贡献。
那要怎么去重呢?
我不会啊?题解说的是啥?这是正常思路吗?难道正常思路不是钦定在某个地方算贡献吗?
等我以后再补。
upd:感谢 born_to_sun 的厉害思路。
具体请看洛谷题解 ,不在这里写了。
upd:原来 zak 也是这么写的。
怎么又是数数。
哦原来是性质题,竟然被我秒了。
通过手玩样例,可以发现:只要填出来的括号序列的任意一个循环同构是合法括号序列,那么这种方案就合法。
为什么?
这很好理解,因为我们要往后无限拼接,那么只要这个括号序列的任意一个循环同构是合法括号序列,那么就一定能拼出来合法的括号序列。
那么怎么判断这个括号序列的任意一个循环同构是合法括号序列呢?
可以发现:一个括号序列的任意一个循环同构是合法括号序列当且仅当它的左右括号数相等。
做完了。
简单数数题。
对于 大小为 , 大小为 时, 和 要放的位置已经固定,只剩下 个数可以随便放。
那么此时的方案数为 。
需要特判 为偶数,因为此时 没地方放。
简单式子题。
通过手推样例的 ,可以发现,答案为
需要特判 。
还有注意取模,一定别取模少了(本来能一遍过就因为取模取少了)。
是不是写的太简短了?那我在这里推一下 吧。
抽象坐标系。
如果是普通的矩形加,那树套树可以轻松解决。
但是这正六边形非常恶心啊,感觉根本做不了。
所以把坐标系变换一下,变成直角坐标系就能做了。
具体怎么变还是请参考题解吧,感觉有点抽象。
枚举区间显然不可做。
但值域是 的,所以可以枚举 。
那么对于每个 ,我们找出它所在的最长区间即可。
假设我们的 已经选好了,那么我们只需要把每个位置挂到值上,然后暴力找即可。
但现在我们需要决策一下 。事实上这让我们的选择更加宽松了。
因为只有当 时,我们才把这个位置挂到这个 上。
每个位置只会遍历一次,复杂度 。
感觉在 CF 上见到过一车这种题?
如果这里的 是 ,那么非常好做,因为这东西是有单调性的,直接枚举左端点然后二分右端点即可。
但这里不是,所以还把 看成 这个整体会很难做。
如果仔细思考会发现一个性质:最优区间的左右端点一定对应着 和 。
证明很容易:在不改变 和 的情况下扩大区间只能让收益减少。
所以,此时的 一定对应着 的下标。
分类讨论一下:
对应 , 对应 。
式子变为 。
设 ,那么要找的就是 。
扫的过程中维护前缀最大即可。
对应 , 对应 。
式子变为 。
设 ,那么要找的就是 。
扫的过程中维护前缀最小即可。
复杂度
看到撤回还以为是操作树,结果是撤销单次操作。
仔细思考这个染色,可以发现:每次加进来的若干线段,只有那条最长的两端点均为黑点的线段是有用的。
那么要怎么维护是否合法?
我一开始想的是区间推平维护区间和,但是这东西完全没法撤销单次操作,如果是回到某个状态倒是能做,离线操作树+ [COCI2015-2016#3] NEKAMELEONI 即可。
那换个思路:非得维护区间和吗?
可以发现,如果一个点没被染过,那么它一定是 0。
所以维护最小值即可。
操作只需要普通的区间加,这东西显然可以撤销。
撤销单次操作加强版:方方方的数据结构
注意:区间加操作可能不合法,需要特判。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include "bits/stdc++.h" #define re register #define int long long #define ls(p) tr[p].ls #define rs(p) tr[p].rs using namespace std;const int maxn=5e5 +10 ,inf=1e18 ;int n,m,rt,segcnt;int a[maxn],b[maxn],pos[maxn];struct tree { int ls,rs,tag,mn; }tr[maxn<<1 ];struct Query { int l,r; }q[maxn];inline void up (int p) {tr[p].mn=min (tr[ls (p)].mn,tr[rs (p)].mn);}inline void add (int l,int r,int val,int p) { tr[p].mn+=val; tr[p].tag+=val; }inline void down (int l,int r,int mid,int p) { if (tr[p].tag){ add (l,mid,tr[p].tag,ls (p)); add (mid+1 ,r,tr[p].tag,rs (p)); tr[p].tag=0 ; } }inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){tr[p].mn=a[l];return ;} int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); }inline void update (int l,int r,int L,int R,int val,int p) { if (L>R||L<=0 ||R>=n+1 ) return ; if (l>=L&&r<=R){add (l,r,val,p);return ;} int mid=(l+r)>>1 ; down (l,r,mid,p); if (L<=mid) update (l,mid,L,R,val,ls (p)); if (R>mid) update (mid+1 ,r,L,R,val,rs (p)); up (p); }inline int query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return tr[p].mn; int mid=(l+r)>>1 ,res=inf; down (l,r,mid,p); if (L<=mid) res=min (res,query (l,mid,L,R,ls (p))); if (R>mid) res=min (res,query (mid+1 ,r,L,R,rs (p))); return res; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (re int i=1 ;i<=n;++i) cin>>pos[i]; int len=0 ; for (re int i=1 ;i<=n;++i){ cin>>a[i]; if (a[i]) b[++len]=i; } build (1 ,n,rt); if (query (1 ,n,1 ,n,1 )==0 ) cout<<"No\n" ; else cout<<"Yes\n" ; for (re int i=1 ,op,l,r,x;i<=m;++i){ cin>>op; if (op==1 ){ cin>>l>>r; l=lower_bound (pos+1 ,pos+n+1 ,l)-pos,r=upper_bound (pos+1 ,pos+n+1 ,r)-pos-1 ; l=b[lower_bound (b+1 ,b+len+1 ,l)-b],r=b[upper_bound (b+1 ,b+len+1 ,r)-b-1 ]; update (1 ,n,l,r,1 ,1 ); } else { cin>>x; update (1 ,n,q[x].l,q[x].r,-1 ,1 ); } q[i]={l,r}; if (query (1 ,n,1 ,n,1 )==0 ) cout<<"No\n" ; else cout<<"Yes\n" ; } return 0 ; }
我是数据结构低手/kk。
遇到这种玩游戏题一般都要观察性质,否则按题意模拟复杂度肯定寄。
首先分析这个操作。
显然第三条规则是没用的,因为这种情况根本不可能出现。
然后可以发现一些性质:
被选到的数的操作时刻一定是一段连续区间。
如果一个数在第一次出现时没有被选上,那么它一定再也不会被选。
那么询问就很清楚了。
设 表示第 次操作后的 。
第 个数要想被选至少 次,需要满足以下要求:
1.
2.
第一个要求保证第 个数第一次出现时就能被选上。
第二个要求保证第 个数能被连续选 次。
第一个要求很好处理,重点是第二个要求。
这个要求事实上就是这个问题:
给定公差 ,每次询问为区间加公差为 的等差数列后的区间最大值。询问互相独立。
第一眼以为很不可做,但关键在公差不变上。
我们令 ,那么我们每次直接查区间最大值,查到的位置 就是对的。
注意是位置,不是值。具体的值需要我们根据公差算一下才能得到。
把 换成 就是这道题了。
实现时,我使用了 ST 表,空间复杂度 ,无法通过。换成线段树就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=2e6 +10 ;int n,m,v;int a[maxn],b[maxn],c[maxn];int st[22 ][maxn],id[22 ][maxn];inline void pre () { for (re int i=1 ;i<=n;++i) b[i]=a[i]; int mx=0 ,id=0 ; for (re int i=1 ;i<=n;++i){ if (b[i]>=mx) id=i;b[id]+=v; c[i]=mx=b[id]; } }inline void init () { for (re int i=1 ;i<=n;++i) st[0 ][i]=a[i]-i*v,id[0 ][i]=i; for (re int j=1 ;j<=21 ;++j){ for (re int i=1 ;i<=n-(1 <<j)+1 ;++i){ if (st[j-1 ][i]>st[j-1 ][i+(1 <<(j-1 ))]) st[j][i]=st[j-1 ][i],id[j][i]=id[j-1 ][i]; else st[j][i]=st[j-1 ][i+(1 <<(j-1 ))],id[j][i]=id[j-1 ][i+(1 <<(j-1 ))]; } } }inline int query (int l,int r) { if (l>r) return 0 ; int k=__lg(r-l+1 ),pos=0 ; if (st[k][l]>st[k][r-(1 <<k)+1 ]) pos=id[k][l]; else pos=id[k][r-(1 <<k)+1 ]; return a[pos]-(pos-l+1 )*v+1 ; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>v; for (re int i=1 ;i<=n;++i) cin>>a[i]; pre ();init (); int ans1=0 ,ans2=0 ; for (re int i=1 ,x,k,res;i<=m;++i){ cin>>x>>k; if (x+k-1 >n) continue ; res=max (c[x-1 ],query (x+1 ,x+k-1 )); ans1^=res,ans2+=res; } cout<<ans1<<" " <<ans2; return 0 ; }
如果 ,那么一定是选和最大的一个长度为 的子区间。
还需要考虑新长出来的部分,这部分是固定的,为 。
如果 ,感性理解一下,可以发现:我们一定会把原来的都拿完,然后来回扫荡。
对于新长出来的那部分,因为我们要从一端走到另一端,所以会少拿 。
一共长出来了 个,所以这部分贡献为 。
感觉说的非常不清楚啊,但我只会感性理解不会证明···
比上题简单。
正着似乎不太好考虑,所以我们倒着考虑。
通过观察样例可以发现,选一段极长后缀一定是一组合法解,这也启示我们倒着考虑。
所以我们倒着扫一遍,能选就选,选不了就停。
然后别忘了前缀里还有一些 的是白送的,也要选上。
那么这为什么是对的呢?
我们正着来看上述操作,可以发现:我们优先让后面的扣,前面的能不扣就不扣。
感性理解一下,前面的越早扣,后面选择的余地就越小,所以能晚扣就晚扣。
然而在做题时我根本没证明,大概想了一下感觉对就做完了,不太会严谨证贪心啊。
简单贪心。
做的时候睡着了,梦里会了。
直接贪就完事了,优先放长度短的,能放就放。
拿线段树模拟这个东西就行。
然后写代码的时候唐完了,离散化后点数为 ,我写个 调半天没调出来,鉴定为睡晕了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include "bits/stdc++.h" #define re register #define int long long #define ls(p) tr[p].ls #define rs(p) tr[p].rs using namespace std;const int maxn=4e5 +10 ,maxm=2e5 +10 ,inf=1e18 ;int n,m,rt,segcnt,ans;int a[maxn],tmp[maxn];struct node { int pos,x; }t[maxn];struct line { int l,r; inline bool operator < (const line &a)const { if (l==a.l) return r<a.r; return l>a.l; } }lin[maxm];struct tree { int ls,rs,mn,tag; }tr[maxn<<1 ];inline void up (int p) {tr[p].mn=min (tr[ls (p)].mn,tr[rs (p)].mn);}inline void add (int p,int val) { tr[p].mn+=val; tr[p].tag+=val; }inline void down (int p) {if (tr[p].tag) add (ls (p),tr[p].tag),add (rs (p),tr[p].tag);tr[p].tag=0 ;}inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){tr[p].mn=a[l];return ;} int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); }inline void update (int l,int r,int L,int R,int val,int p) { if (l>=L&&r<=R){add (p,val);return ;} int mid=(l+r)>>1 ; down (p); if (L<=mid) update (l,mid,L,R,val,ls (p)); if (R>mid) update (mid+1 ,r,L,R,val,rs (p)); up (p); }inline int query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return tr[p].mn; int mid=(l+r)>>1 ,res=inf; down (p); if (L<=mid) res=min (res,query (l,mid,L,R,ls (p))); if (R>mid) res=min (res,query (mid+1 ,r,L,R,rs (p))); return res; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>m>>n; for (re int i=1 ;i<=m;++i) cin>>lin[i].l>>lin[i].r; for (re int i=1 ;i<=n;++i) cin>>t[i].pos>>t[i].x,tmp[i]=t[i].pos; sort (tmp+1 ,tmp+n+1 ); int cnt=unique (tmp+1 ,tmp+n+1 )-(tmp+1 ); memset (a,0x3f ,sizeof a); for (re int i=1 ;i<=n;++i){ t[i].pos=lower_bound (tmp+1 ,tmp+cnt+1 ,t[i].pos)-tmp; a[t[i].pos]=min (a[t[i].pos],t[i].x); } for (re int i=1 ;i<=m;++i){ lin[i].l=lower_bound (tmp+1 ,tmp+cnt+1 ,lin[i].l)-tmp; lin[i].r=upper_bound (tmp+1 ,tmp+cnt+1 ,lin[i].r)-tmp-1 ; } sort (lin+1 ,lin+m+1 ); build (1 ,cnt,rt); for (re int i=1 ;i<=m;++i) if (query (1 ,cnt,lin[i].l,lin[i].r,1 )>0 ) update (1 ,cnt,lin[i].l,lin[i].r,-1 ,1 ),++ans; cout<<ans; return 0 ; }
结论:。
推荐去做[清华集训2012] 模积和 ,加深一下印象。
第一眼以为模板整除分块,结果数据范围 。
我们思考一下 和 的增量是什么。
可以发现,当 为 的约数时, 会比 大 1。
因为还带了个 ,所以答案的增量就是 的约数和。
这东西显然可以线性筛,然后做完了。
注意:我们求的是增量,所以还得求个前缀和。
不是哥们,怎么随便一交就最优解了?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e6 +10 ;int n,cnt;int pri[maxn],d[maxn]; bitset<maxn> vis;inline void sieve () { d[1 ]=1 ; for (re int i=2 ;i<=n;++i){ if (!vis[i]) pri[++cnt]=i,d[i]=i+1 ; for (re int j=1 ;j<=cnt&&i*pri[j]<=n;++j){ vis[i*pri[j]]=1 ; if (i%pri[j]==0 ){d[i*pri[j]]=(d[i]-d[i/pri[j]])*pri[j]+d[i];break ;} else d[i*pri[j]]=d[i]*d[pri[j]]; } } for (re int i=2 ;i<=n;++i) d[i]+=d[i-1 ]; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; sieve (); for (re int i=1 ;i<=n;++i) cout<<n*i-d[i]<<" " ; return 0 ; }
读完题就想到了这道题:HDU 7446 梦中的地牢战斗 。
看到这数据范围应该能直接想到暴力状压吧。
设 表示当前在 ,时间为 ,拿到的西瓜状态为 的最少次数。
转移就是从不走或者走一步转移来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=6 ,maxt=110 ,maxm=(1 <<10 )+10 ,inf=1e18 ;int h,w,T,sx,sy,n,m,sum,S,ans;int mp[maxn][maxn][maxt];int f[maxn][maxn][maxt][maxm];int dx[5 ]={0 ,0 ,1 ,-1 };int dy[5 ]={1 ,-1 ,0 ,0 };signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>h>>w>>T>>sx>>sy>>n>>m;S=(1 <<m)-1 ; for (re int i=1 ,t1,t2,op;i<=n;++i){ cin>>t1>>t2>>op; sum+=op; for (re int t=t1,x,y;t<t2;++t){ cin>>x>>y; if (!op) mp[x][y][t]=-1 ; else mp[x][y][t]=1 <<(sum-1 ); } } if (mp[sx][sy][1 ]==-1 ){cout<<-1 ;return 0 ;} memset (f,0x3f ,sizeof f); f[sx][sy][1 ][mp[sx][sy][1 ]]=0 ; for (re int t=2 ;t<=T;++t){ for (re int x=1 ;x<=w;++x){ for (re int y=1 ;y<=h;++y){ if (mp[x][y][t]==-1 ) continue ; if (mp[x][y][t-1 ]!=-1 ) for (re int s=0 ;s<=S;++s) f[x][y][t][s|mp[x][y][t]]=min (f[x][y][t][s|mp[x][y][t]],f[x][y][t-1 ][s]); for (re int i=0 ;i<4 ;++i){ int prex=x+dx[i],prey=y+dy[i]; if (prex<1 ||prex>w||prey<1 ||prey>h) continue ; if (mp[prex][prey][t-1 ]==-1 ) continue ; for (re int s=0 ;s<=S;++s) f[x][y][t][s|mp[x][y][t]]=min (f[x][y][t][s|mp[x][y][t]],f[prex][prey][t-1 ][s]+1 ); } } } } ans=inf; for (re int x=1 ;x<=w;++x){ for (re int y=1 ;y<=h;++y){ ans=min (ans,f[x][y][T][S]); } } cout<<(ans==inf?-1 :ans); return 0 ; }
这种题我竟然想了大于 0.5 h,还是水平太低了。
先考虑序列怎么做。
设 为前缀积。
对于一次修改中所有会受影响的点 ,它会加上 。
所以一个点的点权为 。
把 提出来,得到 。
对于每次修改,后面那个东西都是确定的,所以修改就是区间加。
但是有个问题:有 0 怎么办?
显然 0 的出现会让上面的操作出错。
但可以发现:如果有 0,那么这前后两段是完全独立的(即对前面段的操作不会影响到后面段,反之同理)。
所以以 0 为断点,把序列分成若干连续段,那么每个连续段是独立的。
对每个连续段分别做上面的东西即可。
然后上树是简单的,这题是子树加单点查,甚至不需要树剖。
根据 0 把原树拆成森林,然后对每部分分别做即可。
代码感觉非常好写,最大难点可能是精度问题?
一个比较麻烦的做法。
首先可以想到线段树,然后考虑怎么合并信息。
我们对每个节点维护:左/右侧 X 极长连续段长度,左侧第一个 ‘)’ 的位置,右侧第一个 ‘(‘ 的位置,该区间左右端点,该区间答案。
两个区间合并能产生新答案,当且仅当:左区间的右端点-右侧 X 极长连续段长度=右侧第一个 ‘(‘ 的位置且右区间的左端点+左侧 X 极长连续段长度=左侧第一个 ‘)’ 的位置。
事实上我写麻烦了,只需要对每个点维护左/右侧第一个 ‘)’ 的位置以及左/右侧第一个 ‘(‘ 的位置即可。
大家可以思考一下怎么做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include "bits/stdc++.h" #define re register #define int long long #define ls(p) tr[p].ls #define rs(p) tr[p].rs using namespace std;const int maxn=2e5 +10 ,inf=1e18 ;int n,m,rt,segcnt;struct data { int ans; int l,r; int lpos,rpos; int len1,len2; inline data () {ans=len1=len2=0 ,lpos=inf,rpos=-inf;} inline void init (int val) { if (val==-1 ) rpos=l,lpos=inf,len1=len2=ans=0 ; if (val==0 ) len1=len2=1 ,lpos=inf,rpos=-inf,ans=0 ; if (val==1 ) lpos=l,rpos=-inf,len1=len2=ans=0 ; } inline data operator + (const data &a)const { data res; res.l=l,res.r=a.r; res.ans=ans+a.ans; res.lpos=min (lpos,a.lpos); res.rpos=max (rpos,a.rpos); if (len1==r-l+1 ) res.len1=len1+a.len1; else res.len1=len1; if (a.len2==a.r-a.l+1 ) res.len2=len2+a.len2; else res.len2=a.len2; if (rpos==r-len2&&a.lpos==a.l+a.len1) ++res.ans; return res; } };struct tree { int ls,rs; data s; }tr[maxn<<1 ];inline void up (int p) {tr[p].s=tr[ls (p)].s+tr[rs (p)].s;}inline void build (int l,int r,int &p) { if (!p) p=++segcnt; tr[p].s.l=l,tr[p].s.r=r; if (l==r){ if (l==1 ) tr[p].s.init (-1 ); else if (l==n) tr[p].s.init (1 ); else tr[p].s.init (0 ); return ; } int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); }inline void update (int pos,int val,int p) { if (tr[p].s.l==tr[p].s.r){tr[p].s.init (val);return ;} int mid=(tr[p].s.l+tr[p].s.r)>>1 ; if (pos<=mid) update (pos,val,ls (p)); else update (pos,val,rs (p)); up (p); }inline data query (int L,int R,int p) { if (tr[p].s.l>=L&&tr[p].s.r<=R) return tr[p].s; int mid=(tr[p].s.l+tr[p].s.r)>>1 ; if (R<=mid) return query (L,R,ls (p)); if (L>mid) return query (L,R,rs (p)); return query (L,R,ls (p))+query (L,R,rs (p)); }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; build (1 ,n,rt);char c; for (re int i=1 ,op,l,r,pos;i<=m;++i){ cin>>op; if (op==1 ){ cin>>pos>>c; if (c=='(' ) update (pos,-1 ,1 ); if (c=='X' ) update (pos,0 ,1 ); if (c==')' ) update (pos,1 ,1 ); } else cin>>l>>r,cout<<query (l,r,1 ).ans<<'\n' ; } return 0 ; }
简单单调队列。
设 表示当前在 的最大价值,转移为
对每个 都做一次单调队列即可。
第一遍没做出来···
首先 ,考虑状压,那么每次操作就是把至多 位取反。
设 表示到达状态 的最小次数,转移为
发现这个转移很像最短路,且没有转移顺序,所以考虑用最短路转移。
暴力建图后跑最短路,复杂度为 。
按理来说过不了,但它过了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=(1 <<15 )+10 ,maxm=55 ,inf=1e18 ;int n,m1,m2,r,S,cnt;int dis[maxn],num[maxn]; bitset<maxn> vis; vector<int > g;struct limit1 { int a,b; }t1[maxm];struct limit2 { int a,b,c; }t2[maxm];inline void init () { for (re int s=0 ;s<=S;++s){ bool flag=1 ; for (re int i=1 ;i<=m1;++i){ bool a=s&(1 <<(t1[i].a-1 )),b=s&(1 <<(t1[i].b-1 )); if (a^b){flag=0 ;break ;} } if (!flag) continue ; for (re int i=1 ;i<=m2;++i){ bool a=s&(1 <<(t2[i].a-1 )),b=s&(1 <<(t2[i].b-1 )),c=s&(1 <<(t2[i].c-1 )); if (!(b^c)&&(a^b)){flag=0 ;break ;} } if (flag) g.push_back (s); } }inline bool check (int s,int t) { int cnt=__builtin_popcount(s^t); return cnt<=r; }inline void spfa () { for (re int s=0 ;s<=S;++s) dis[s]=inf; queue<int > q; q.push (0 );vis[0 ]=1 ,dis[0 ]=0 ,num[0 ]=1 ; while (!q.empty ()){ int u=q.front ();q.pop ();vis[u]=0 ; for (auto v:g){ if (!check (u,v)) continue ; if (dis[v]>=dis[u]+1 ){ if (dis[v]==dis[u]+1 ) num[v]+=num[u]; else num[v]=num[u]; dis[v]=dis[u]+1 ; if (!vis[v]) q.push (v),vis[v]=1 ; } } } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m1>>m2>>r;r=min (r,n);S=(1 <<n)-1 ; for (re int i=1 ;i<=m1;++i) cin>>t1[i].a>>t1[i].b; for (re int i=1 ;i<=m2;++i) cin>>t2[i].a>>t2[i].b>>t2[i].c; init ();spfa (); if (dis[S]==inf) cout<<-1 <<' ' <<0 ; else cout<<dis[S]<<' ' <<num[S]; return 0 ; }
正解:
考虑优化建图。
我们把一个点拆成 个点:一个真点(记作 )和 个假点(记作 ,表示还能和当前状态差 位)。
我们这样建图:
向 连边权为 1 的边
向 连边权为 0 的边(, 为和 恰好差一位的状态)
向 连边权为 0 的边()
复杂度
但有个问题,最短路计数怎么记?
暂时不会。
题解做法正确性不显然,我也不会。
upd:这个做法好像求不了方案数。
大家看题解自己悟吧。
建图时,如果两条绳子有交点,要把交点独立出来。
建完图以后,从每个点跑一次最短路。
以一个点作为起点跑最短路后,可以得到每个点被点燃的最早时间。
然后枚举绳子,得到每条绳子燃烧完的时间。
注意中点不能作为点燃点。
复杂度取决于你怎么写最短路。
别写 SPFA 了。
加强版:花园
这道题先断环成链然后设个 再把转移写出来发现能矩阵加速然后就做完了。
来看加强版。
根据弱化版的状态,我们应该把前 个位置的状态都记下来。
又因为 ,所以考虑状压。
设 从 转移过来,考虑 要满足什么限制,可以发现:
状态 里 的个数
的后 位与 的前 位完全相同
代码实现上,可以这样写
关于断环成链的部分,对于初始状态 ,DP 后的 就是答案(因为这是个环)。
容易发现这东西能用矩阵加速。
构造转移矩阵 ,其中 表示状态 能否转移到状态 。
枚举初始状态和统计答案都不变,把 的递推改成 的矩阵快速幂即可。
设矩阵大小 ,则复杂度为
简单题,把通项求出来即可。
如果你不会求常系数线性递推方程的通项,可以看我的这篇文章 。
带大家一起算一下吧。
先整理成标准形式
其特征方程为
其特征根为
所以通解为
因为 为指数函数,且 为一重特征根,所以设特解 。
代入,得
解得
通解为
将初值代进去,解得
综上,通解为
思考方向又反了啊···
题目中两种行走方式本质相同,我们下面只考虑一种:步长为 。
设走一步的代价为 。
不难想到 DP:设 表示到达点 的最大收益,则转移为
考虑如何快速维护到一个点距离恰好为 的点集。
我的想法是:因为起点只有 1,所以可以给每个点维护若干条从 1 开始的长为 的链。
但每个点最多会维护 条链,所以复杂度 ,寄了。
感觉这东西真不太能做吧?
所以我们换个角度思考:既然我们无法维护出从哪转移来,那能不能维护点 向后面点的贡献?
这似乎是可以维护的。
重新设计 DP:设 表示从点 还要走 步的最大收益,设存在边 ,则转移为
这个 DP 的思路是:我们从点 往后走,即先把代价减去。如果能贡献到答案,那么它一定会走到后面的一个点然后把答案贡献上去。
一定要初始成极小值,因为我们需要接受答案为负数(刚减去代价时)。
注意特判 。
复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=2e5 +10 ,maxm=3e5 +10 ,maxk=55 ;int n,m,num,A,B,wa,wb,cnt,ans;int head[maxn],in[maxn];int a[maxn],f[maxn][maxk];struct edge { int to,nxt; }e[maxm];inline void add (int u,int v) { ++in[v]; e[++cnt]={v,head[u]}; head[u]=cnt; }inline void topo () { memset (f,-0x3f ,sizeof f); queue<int > q;q.push (1 );f[1 ][0 ]=a[1 ]; while (!q.empty ()){ int u=q.front ();q.pop (); for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (!(--in[v])) q.push (v); for (re int j=B;j>1 ;--j) f[v][j-1 ]=max (f[v][j-1 ],f[u][j]); f[v][0 ]=max (f[v][0 ],f[u][1 ]+a[v]); if (A!=1 ) f[v][A-1 ]=max (f[v][A-1 ],f[u][0 ]-wa); else f[v][0 ]=max (f[v][0 ],f[u][0 ]-wa+a[v]); if (B!=1 ) f[v][B-1 ]=max (f[v][B-1 ],f[u][0 ]-wb); else f[v][0 ]=max (f[v][0 ],f[u][0 ]-wb+a[v]); } } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>num>>n>>m>>A>>B>>wa>>wb; for (re int i=1 ,pos,val;i<=num;++i) cin>>pos>>val,a[pos]+=val; for (re int i=1 ,u,v;i<=m;++i) cin>>u>>v,add (u,v); topo (); int ans=0 ; for (re int i=1 ;i<=n;++i) ans=max (ans,f[i][0 ]); cout<<ans; return 0 ; }
一开始看到期望还以为是困难题,结果是经典拆贡献分讨题。
不知道题解是怎么分的,感觉我分的似乎有点麻烦?不过懒得合并了。
以下内容省略了很多过程,直接给出了结果,大家可以思考一下是怎么得到的。
首先期望是假的,我们只需要算出距离和然后除以总方案数即可。
我们可以把一颗基环树画成这样:
我们把环上点记作 ,其环外子树大小记作 ,下文的子树都指环外子树。
显然,对于点对 ,若它们之间的路径经过了环上点,那么就一定会有两条路径,否则只有一条路径。
对于经过环上点的点对 ,可以发现它们的贡献可以拆成两部分:
1.环长的一半
2.
对于不经过环上点的点对 ,它们只有距离贡献,没有环贡献。
所以我们对点对 是否经过环上点分类讨论。
不经过环上点
容易发现,这两个点一定都在同一颗子树内,且不经过根节点。
那么答案就为
差分一下,变为
前面这个东西是经典问题,考虑每条边的贡献即可。
因为点对是有序的,所以答案要 ×2。
经过环上点
我们先计算环贡献。
这里我又拆成了三部分:子树内(不包括根)经过根节点的贡献,子树(不包括根)与外部点的贡献以及所有环上点的贡献。
设 为根的儿子,环长的一半为 ,则第一部分答案为
第二部分答案为
第三部分答案为
然后计算距离贡献。
对于点对 ,我们需要计算 。
容易发现,点 会和所有子树(不包括根)外的点产生这部分贡献,所以点 的贡献为
所以总贡献为
因为点对是有序的,所以这部分贡献要 ×2。
感觉我的代码极其丑陋···
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=3e5 +10 ,mod=19260817 ,inv2=9630409 ;int n,cnt=1 ;int ans,sum,rt,num;int head[maxn],dis[maxn],siz[maxn];struct edge { int to,nxt,w; }e[maxn<<1 ];inline int qpow (int a,int b) { int res=1 ; while (b){ if (b&1 ) res=res*a%mod; b>>=1 ; a=a*a%mod; } return res; }inline int Inv (int x) {return qpow (x,mod-2 );}inline void add (int u,int v,int w) { e[++cnt]={v,head[u],w}; head[u]=cnt; }int tim;int dfn[maxn],lst[maxn]; bitset<maxn> vis;inline void get (int u,int v) {vis[u]=1 ;while (v!=u) vis[v]=1 ,v=e[lst[v]^1 ].to;}inline void dfs (int u) { dfn[u]=++tim; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (!dfn[v]) lst[v]=i,dfs (v); else if (i^1 !=lst[u]&&dfn[v]>dfn[u]) get (u,v); } }inline void dfs1 (int u,int fa) { sum+=dis[u];siz[u]=1 ; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==fa||vis[v]) continue ; dis[v]=dis[u]+e[i].w; dfs1 (v,u); siz[u]+=siz[v]; } }inline void dfs2 (int u,int fa) { for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==fa||vis[v]) continue ; ans=(ans+2 *e[i].w%mod*siz[v]%mod*(siz[rt]-siz[v])%mod)%mod; dfs2 (v,u); } }bool flag;inline void dfs3 (int u,int fa) { if (u==rt&&sum){flag=1 ;return ;} for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==fa||!vis[v]) continue ; sum+=e[i].w; dfs3 (v,u); if (flag) return ; } }inline void dfs4 (int u,int fa) { if (fa==rt) ans=(ans+(siz[rt]-1 -siz[u])*siz[u]*sum%mod)%mod; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==fa||vis[v]) continue ; dfs4 (v,u); } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; for (re int i=1 ,u,v,w;i<=n;++i) cin>>u>>v>>w,add (u,v,w),add (v,u,w); dfs (1 ); for (re int i=1 ;i<=n;++i){ if (vis[i]){ sum=0 ;rt=i; dfs1 (rt,0 );dfs2 (rt,0 ); ans=((ans-sum*2 )%mod+mod)%mod; ans=(ans+2 *(n-(siz[rt]-1 ))*sum%mod)%mod; } } for (re int i=1 ;i<=n;++i){ if (vis[i]){ sum=0 ;rt=i; dfs3 (i,0 ); sum=sum*inv2%mod; break ; } } for (re int i=1 ;i<=n;++i){ if (vis[i]){ rt=i; dfs4 (i,0 ); ans=(ans+(siz[i]-1 )%mod*(n-(siz[i]-1 ))%mod*sum)%mod; ans=(ans+n*sum%mod)%mod; } } cout<<ans*Inv (n*n%mod) %mod ; return 0 ; }
原来是套路题吗?感觉没怎么见过啊,看来要加训了。
关于区间加多项式
设区间加的范围为 ,。
根据二项式定理,。
对于每个位置 ,我们将其视为维护了一个 的 次多项式。
那么区间加操作相当于对 里的每个多项式都加上 。
因为每一项都加的一样,所以区间加被转化为了做 次区间加。
因为没有边问边查,所以可以差分维护。
复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include "bits/stdc++.h" #define re register using namespace std;typedef unsigned long long u64;typedef unsigned int u32; u32 MT[624 ],idx;void _init(u32 seed){ MT[0 ]=seed; idx=0 ; for (int i=1 ;i<624 ;++i) MT[i]=(0x6c078965 *(MT[i-1 ]^((MT[i-1 ])>>30 )+i)); }void _gene(){ for (int i=0 ;i<624 ;++i){ int x=MT[i]&0x80000000 +(MT[(i+1 )%624 ]&0x7fffffff ); MT[i]=MT[(i+397 )%624 ]^(x>>1 ); if (x&2 )MT[i]^=0x9908b0df ; } } u32 _calc(){ if (!idx) _gene(); int x=MT[idx]; x^=x>>11 ,x^=(x<<7 )&(0x9d2c5680 ); x^=(x<<15 )&0xefc60000 ,x^=x>>18 ; idx=(idx+1 )%624 ; return x; } u64 _get(){u64 ret=_calc()*_calc(); return ret;} u64 _get(u64 _l,u64 _r){return _get()%(_r-_l+1ull )+_l;}void input (int &_n,int &_m,int &_q,int *_S,int *_L,u64 *_W,int *_K) { u32 seed; scanf ("%d%d%d%u" ,&_n,&_m,&_q,&seed); _init(seed); int i=1 ; if (_n>100 ) for (;i<=_q/4 ;++i){ int _a=_get(1 ,_n-100 ),_b=_get(_a+_m,_a+_m+1 ),_l=_b-_a+1 ,_k=_get(0 ,_m); u64 _w=_get(); _S[i]=_a,_L[i]=_l,_W[i]=_w,_K[i]=_k; } if (_n>100 ) for (;i<=_q/2 ;++i){ int _a=_get(1 ,100 ),_b=_get(_n-100 ,_n),_l=_b-_a+1 ,_k=_get(0 ,_m); u64 _w=_get(); _S[i]=_a,_L[i]=_l,_W[i]=_w,_K[i]=_k; } for (;i<=_q;++i){ int _a=_get(1 ,_n),_b=_get(1 ,_n); if (_a>_b) swap (_a,_b); int _l=_b-_a+1 ,_k=_get(0 ,_m); u64 _w=_get(); _S[i]=_a,_L[i]=_l,_W[i]=_w,_K[i]=_k; } }void output (int n,u64 *R) { u64 ret=n^_get(); for (int i=1 ;i<=n;i++) ret^=_get()+R[i]; printf ("%llu\n" ,ret); }const int maxn=5e5 +10 ,maxq=1e6 +10 ,maxm=10 ;int n,m,q; u64 C[maxm][maxm],a[maxn][maxm],ans[maxn],w[maxq];int s[maxq],len[maxq],k[maxq];inline void init () { C[0 ][0 ]=1 ; for (re int i=1 ;i<=m;++i){ C[i][0 ]=1 ; for (re int j=1 ;j<=i;++j) C[i][j]=C[i-1 ][j]+C[i-1 ][j-1 ]; } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); input (n,m,q,s,len,w,k); init (); for (re int i=1 ;i<=q;++i){ u64 tmp=w[i]; for (re int j=k[i];~j;--j){ a[s[i]][j]+=tmp*C[k[i]][j]; a[s[i]+len[i]][j]-=tmp*C[k[i]][j]; tmp*=(1 -s[i]); } } for (re int i=1 ;i<=n;++i){ u64 tmp=1 ; for (re int j=0 ;j<=m;++j){ a[i][j]+=a[i-1 ][j]; ans[i]+=tmp*a[i][j]; tmp*=i; } } output (n,ans); return 0 ; }
第一眼想到了[SCOI2014] 方伯伯的商场之旅 ?
如果我们知道了选哪个位置当作避难所,那么答案和修改是很好做的,拆一下绝对值然后就可以线段树轻松维护了。
那么关键问题是:如何选避难所?
可以感性的得到一个结论:一定要选到有人的点上。
首先想到了二分,然后想到了带权中位数。
二分没想明白单调性,带权中位数也没太想明白。
然后就没想出来。
下面仔细说一下单调性。
设避难所位置为 ,。
结论:最优的 一定要使 和 尽可能接近 。
证明:
假设当前的 满足 。
那么当 变为 时,答案的增量为 。
这显然会让答案更劣,所以我们不会让 变为 。
经过这样的操作,我们会发现 和 都在接近 。
所以可以二分找到这个 。
二分+线段树是两个 ,改为线段树上二分就是一个 。
注意:最终二分到的位置可能不是最优解,需要再求一下 和 。
这是不是就是带权中位数啊?
这是紫?这是紫?这是紫?
容易发现答案是前缀和区间求和然后除以区间长度。
所以维护线段树维护前缀和即可。
一个点 可以到达的点 ,需要满足存在 到 的一条路径,使得边权最大值 的点权。
容易想到 Kruskal 重构树。
设点 向上倍增找到的最浅点为 , 能覆盖到的叶子区间为 。
那么现在问题转化为了:
给定若干线段,求完全覆盖区间 的最小线段数。
这是经典贪心,排序后双指针即可。
然后现在还要求:
每次删除一条线段,求删除该线段后的最小线段数。
可以发现,只有删除的线段在最优解里用过且只有一条和它一模一样的线段时,答案才可能发生变化,所以只需要考虑这种情况。
因为我们是从树上转化来的线段,所以可以保证线段要么包含要么不交。
那么把这条线段删除以后,我们只需要看被它包含的线段能不能把它补上。
假设第一条是要被删的线段,那么我们只需要考虑第二层这些线段,而不用考虑第二层以下的线段(即被第二层线段完全包含的那些线段)。
所以可以预处理出:如果这条线段被删掉,用来补充的第二层线段有哪些。
每条线段最多被遍历一次,所以这部分复杂度为 。
事实上,上面说的这一堆和题解说的在重构树上 dfs 是完全一致的,只不过我把它抽象成了线段。
而且这样做写起来也挺难写的(因为我不会写双指针),所以理解一下意思就行了。
不过这种把树上问题转化为区间问题的思路还是挺有启发意义的?
贪心只能纯靠猜,不过这次猜对了。
对于求最大值,不难想到排序后,选最左侧和最右侧的各 个点就是最优的。
证明可以考虑反证法:把其中的一个点换成一个靠中间的点一定不优,显然让答案变小了。
那么怎么求次大值呢?
经过手玩样例和打表,发现:把第 个点(即最左侧点中最靠右的那个)换成它右边那个或把第 个点(即最右侧点中最靠左的那个)换成它左边那个就是次大。
然后交上去,发现错了。
事实上,我们换到的新点的值可能和原来一样,这时答案不变。
所以结论应该是:把第 个点(即最左侧点中最靠右的那个)换成它右边第一个和它不同的点或把第 个点(即最右侧点中最靠左的那个)换成它左边第一个和它不同的点就是次大。
当然,有可能找不到不一样的点,那就换成左侧/右侧的继续找就行了。
注意判无解。
加强版:「TOCO Round 1」History
对于维护 级表亲信息的题目,通常可以用一种 bfs 序+dfs 序的方式维护。
我们对每个深度维护出它对应的 bfs 序,然后把 bfs序对应到 dfs序上。
设要求点 的 级表亲信息,那么我们先求出来 的 级祖先 ,然后在 所在深度对应的 bfs序区间上,二分找出 dfs序在 的子树对应的区间里的一段区间。
这段区间就是 的 级表亲(包括 )。
所以我们维护的信息要在 bfs序上维护。
感觉说的有点抽象,具体可以看代码,应该还是挺好懂的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include "bits/stdc++.h" #define re register using namespace std;const int maxn=1e6 +10 ;int n,q,cnt,tim;int head[maxn],fa[maxn][21 ],seq[maxn];struct edge { int to,nxt; }e[maxn];struct node { int l,r,dep,bfn; }t[maxn];struct line { int l,r; }Dep[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} #undef lb(x) }a;inline void add (int u,int v) { e[++cnt]={v,head[u]}; head[u]=cnt; }inline void bfs (int s) { queue<int > q; q.push (s);t[s].dep=1 ,Dep[1 ].l=++tim,t[s].bfn=tim; while (!q.empty ()){ int u=q.front ();q.pop (); Dep[t[u].dep].r=t[u].bfn; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; t[v].dep=t[u].dep+1 ; t[v].bfn=++tim; if (!Dep[t[v].dep].l) Dep[t[v].dep].l=tim; q.push (v); } } }inline void dfs (int u) { t[u].l=++tim;seq[t[u].bfn]=tim;a.add (t[u].bfn,1 ); for (re int j=1 ;j<=20 ;++j) fa[u][j]=fa[fa[u][j-1 ]][j-1 ]; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; dfs (v); } t[u].r=tim; }inline int find (int u,int dep) { for (re int i=20 ;i>=0 ;--i) if (dep>=(1 <<i)) u=fa[u][i],dep-=(1 <<i); return u; }inline int solve (int u,int k) { int f=find (u,k); if (!f) return 0 ; int l=Dep[t[u].dep].l,r=Dep[t[u].dep].r,mid,L=t[f].l,R=t[f].r; while (l<=r){ mid=(l+r)>>1 ; if (seq[mid]>=t[f].l) L=mid,r=mid-1 ; else l=mid+1 ; } l=Dep[t[u].dep].l,r=Dep[t[u].dep].r; while (l<=r){ mid=(l+r)>>1 ; if (seq[mid]<=t[f].r) R=mid,l=mid+1 ; else r=mid-1 ; } return a.query (R)-a.query (L-1 )-1 ; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>q; for (re int v=2 ,u;v<=n;++v) cin>>u,add (u,v),fa[v][0 ]=u; bfs (1 );tim=0 ;dfs (1 ); for (re int i=1 ,u,k;i<=q;++i) cin>>u>>k,cout<<solve (u,k)<<' ' ; return 0 ; }
想出来了,但现在不会写 LCT 了(悲)。
容易发现,若 有环,则所有包含 的区间都必定有环。
所以我们只需要对于每个 ,求出第一个使得 有环的 即可。
可以发现, 关于 单调不降,所以可以双指针。
维护连通性用 LCT 即可。
然后是怎么算贡献。
对于一个有环区间 ,它会贡献到 。
所以对于每个 ,都有 次区间加,差分以后变成 次单点加。
可以发现,差分以后是对 加上 ,对 区间减 1。
后面这个可以再差分一下,不过无所谓了,反正只有 次区间减,多带个 应该也没啥。
LCT 是从模板贺的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include "bits/stdc++.h" #define re register #define int long long #define ls(x) tr[x].ch[0] #define rs(x) tr[x].ch[1] #define fa(x) tr[x].fa #define get(x) (rs(fa(x))==x) #define isroot(x) (ls(fa(x))!=x&&rs(fa(x))!=x) using namespace std;const int maxn=2e5 +10 ;int n;int a[maxn];int s1[maxn],s2[maxn];struct edge { int u,v; }e[maxn];struct node { int fa,ch[2 ],tag; }tr[maxn];inline void rev (int x) {swap (ls (x),rs (x));tr[x].tag^=1 ;}inline void down (int x) {if (tr[x].tag){rev (ls (x)),rev (rs (x)),tr[x].tag=0 ;}}inline void update (int x) {if (!isroot (x)){update (fa (x));}down (x);}inline void rotate (int x) { int y=fa (x),z=fa (y),t=get (x); if (!isroot (y)) tr[z].ch[get (y)]=x; fa (tr[x].ch[t^1 ])=y,tr[y].ch[t]=tr[x].ch[t^1 ]; tr[x].ch[t^1 ]=y; fa (y)=x,fa (x)=z; }inline void splay (int x) { update (x); for (re int f=fa (x);f=fa (x),!isroot (x);rotate (x)){ if (!isroot (f)) rotate (get (f)==get (x)?f:x); } }inline void access (int x) {for (re int p=0 ;x;p=x,x=fa (x)){splay (x),rs (x)=p;}}inline void makeroot (int x) {access (x),splay (x),rev (x);}inline int find (int x) { access (x),splay (x); while (ls (x)) x=ls (x),down (x); splay (x); return x; }inline void split (int x,int y) {makeroot (x),access (y),splay (y);}inline void link (int x,int y) {makeroot (x);if (find (y)!=x) fa (x)=y;}inline void cut (int x,int y) {makeroot (x);if (find (y)==x&&fa (y)==x&&!ls (y)) fa (y)=rs (x)=0 ;}inline void add (int l,int r) {s1[l]+=(n-r+1 );s1[r+1 ]-=(n-r+1 );++s2[r+1 ];}signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; for (re int i=1 ;i<=n;++i) cin>>e[i].u>>e[i].v; int now=1 ; for (re int i=1 ;i<=n;++i){ while (find (e[i].u)==find (e[i].v)){ add (now,i); cut (e[now].u,e[now].v); ++now; } link (e[i].u,e[i].v); } for (re int i=1 ;i<=n;++i){ s1[i]+=s1[i-1 ];s2[i]+=s2[i-1 ]; cout<<s1[i]+(n-i+1 )*s2[i]<<' ' ; } return 0 ; }
没有样例解释,且题意不明,被硬控 0.5h。
说一下坑点:
如果现在选了 ,那么只有下一个选的 能受到 的加成。
物品体积可能为负。
要求的是选体积和恰好为 的物品。
弄明白题以后,状态和转移都是好想的,就一个背包变形。
设 表示前 个选了体积恰好为 的物品,转移为
因为有负数,所以要加一个偏移量。
弱化版:[HEOI2012] 采花
弱弱化版:[SDOI2009] HH的项链
第一眼以为是莫队简单题,结果一看数据范围···
结果我发现我连HH的项链 都不会做,所以我们从最简单的这道题入手。
二维数点做法和莫队做法不再赘述,因为它们都解决不了我们的加强版。
对于一种颜色,我们考虑在最后一次出现时算它的贡献。
我们将询问离线,按右端点升序排序,然后维护一个指针从左向右扫。
我们对每种颜色维护它上一次出现的位置。
当扫到一种出现过的颜色时,我们把上一次出现的位置的贡献减去,然后加到当前位置,然后更新上一次出现的位置。
把当前询问扫完以后,我们直接区间求和即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e6 +10 ;int n,m;int a[maxn],lst[maxn],ans[maxn];struct query { int l,r,id; inline bool operator < (const query &a)const {return r<a.r;} }q[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} inline int query (int l,int r) {return query (r)-query (l-1 );} #undef lb(x) }b;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; for (re int i=1 ;i<=n;++i) cin>>a[i]; cin>>m; for (re int i=1 ;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i; sort (q+1 ,q+m+1 ); int pos=1 ; for (re int i=1 ;i<=m;++i){ for (re int j=pos;j<=q[i].r;++j){ if (lst[a[j]]) b.add (lst[a[j]],-1 ); b.add (j,1 ); lst[a[j]]=j; } pos=q[i].r+1 ; ans[q[i].id]=b.query (q[i].l,q[i].r); } for (re int i=1 ;i<=m;++i) cout<<ans[i]<<'\n' ; return 0 ; }
然后来看弱化版。
对于一种颜色,我们考虑在倒数第二次出现时算它的贡献。
我们将询问离线,按右端点升序排序,然后维护一个指针从左向右扫。
我们对每种颜色维护它上一次出现的位置和上上次出现的位置。
当扫到一种出现过的颜色时,我们把上上次出现的位置的贡献减去,然后加到上一次出现的位置,然后更新上一次出现的位置和上上次出现的位置。
代码基本一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=2e6 +10 ;int n,m,c;int a[maxn],lst1[maxn],lst2[maxn],ans[maxn];struct query { int l,r,id; inline bool operator < (const query &a)const {return r<a.r;} }q[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} inline int query (int l,int r) {return query (r)-query (l-1 );} #undef lb(x) }b;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>c>>m; for (re int i=1 ;i<=n;++i) cin>>a[i]; for (re int i=1 ;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i; sort (q+1 ,q+m+1 ); int pos=1 ; for (re int i=1 ;i<=m;++i){ for (re int j=pos;j<=q[i].r;++j){ if (!lst1[a[j]]) lst1[a[j]]=j; else if (!lst2[a[j]]){ b.add (lst1[a[j]],1 ); lst2[a[j]]=j; } else { b.add (lst1[a[j]],-1 ),b.add (lst2[a[j]],1 ); lst1[a[j]]=lst2[a[j]],lst2[a[j]]=j; } } pos=q[i].r+1 ; ans[q[i].id]=b.query (q[i].l,q[i].r); } for (re int i=1 ;i<=m;++i) cout<<ans[i]<<'\n' ; return 0 ; }
所以根据这个套路,我们对于维护出现次数 的颜色的个数的问题,依旧可以这么做:
对于一种颜色,我们考虑在倒数第 次出现时算它的贡献。
我们将询问离线,按右端点升序排序,然后维护一个指针从左向右扫。
我们对每种颜色维护它上一次出现的位置,上上次出现的位置···上 次出现的位置。
当扫到一种出现过的颜色时,我们把上 次出现的位置的贡献减去,然后加到上 次出现的位置,然后更新上一次出现的位置,上上次出现的位置···上 次出现的位置。
那么现在来看这道加强版。
设区间 内颜色 出现次数为 ,出现总次数为 ,则该颜色能产生贡献当且仅当
把绝对值拆一下,可以得到
所以问题转化为:求 。
如果所有颜色的范围限制都一样,那还好做,用出现次数 的减去 的即可。
但是现在范围不一样,再直接套用之前算贡献的方法似乎不能做。
所以我们现在这样算贡献:对于颜色 ,我们在出现次数为 的地方 +1,在出现次数为 的地方 -1,这样求前缀和得到的就是正确贡献。
我们将询问离线,按左端点升序排序,然后维护一个指针从左向右扫。
对于当前询问区间 ,我们让指针扫到 时停止。
对于扫到的颜色,我们让它出现次数为 的地方向后跳一个,出现次数为 的地方也向后跳一个。
那么这个询问的答案就是右端点求个前缀和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e6 +10 ;int n,m,k;int a[maxn],lst[maxn],nxt[maxn],ans[maxn];int lc[maxn],rc[maxn];int buc[maxn]; bitset<maxn> vis;struct query { int l,r,id; inline bool operator < (const query &a)const {return l<a.l;} }q[maxn];struct node { int l,r; }t[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} #undef lb(x) }b;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>k; for (re int i=1 ;i<=n;++i) cin>>a[i],++buc[a[i]],lst[a[i]]=n+1 ;nxt[n+1 ]=n+1 ; for (re int i=1 ;i<=n;++i) t[a[i]].l=max (1ll ,(buc[a[i]]-k+1 )>>1 ),t[a[i]].r=(buc[a[i]]+k)>>1 ; for (re int i=n;i>=1 ;--i) nxt[i]=lst[a[i]],lst[a[i]]=i; for (re int i=1 ,res;i<=n;++i){ if (!vis[a[i]]){ vis[a[i]]=1 ;lc[a[i]]=rc[a[i]]=i; res=1 ;while (res<t[a[i]].l&&lc[a[i]]^(n+1 )) ++res,lc[a[i]]=nxt[lc[a[i]]];b.add (lc[a[i]],1 ); res=0 ;while (res<t[a[i]].r&&rc[a[i]]^(n+1 )) ++res,rc[a[i]]=nxt[rc[a[i]]];b.add (rc[a[i]],-1 ); } } for (re int i=1 ;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i; sort (q+1 ,q+m+1 ); int pos=1 ; for (re int i=1 ;i<=m;++i){ for (re int j=pos;j<q[i].l;++j){ b.add (lc[a[j]],-1 ),b.add (rc[a[j]],1 ); lc[a[j]]=nxt[lc[a[j]]],rc[a[j]]=nxt[rc[a[j]]]; b.add (lc[a[j]],1 ),b.add (rc[a[j]],-1 ); } pos=q[i].l; ans[q[i].id]=b.query (q[i].r); } for (re int i=1 ;i<=m;++i) cout<<ans[i]<<'\n' ; return 0 ; }
哦,那好像这个加强版才是通法。
我们对于维护出现次数在 的颜色的个数的问题,可以这么做:
对于一种颜色,我们考虑在第 次出现时加上它的贡献,在第 次出现时减去贡献。
我们将询问离线,按左端点升序排序,然后维护一个指针从左向右扫。
我们对每种颜色维护它当前位置下一个这种颜色的位置。
当扫到一种出现过的颜色时,我们把 +1 的贡献和 -1 的贡献都往后跳一个。
答案为查询区间右端点做前缀和。
HH的项链 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e6 +10 ;int n,m,k;int a[maxn],lst[maxn],nxt[maxn],ans[maxn];int lc[maxn]; bitset<maxn> vis;struct query { int l,r,id; inline bool operator < (const query &a)const {return l<a.l;} }q[maxn];struct node { int l,r; }t[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} #undef lb(x) }b;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; for (re int i=1 ;i<=n;++i) cin>>a[i],lst[a[i]]=n+1 ;nxt[n+1 ]=n+1 ; for (re int i=n;i>=1 ;--i) nxt[i]=lst[a[i]],lst[a[i]]=i; for (re int i=1 ;i<=n;++i){ if (!vis[a[i]]){ vis[a[i]]=1 ; lc[a[i]]=i;b.add (lc[a[i]],1 ); } } cin>>m; for (re int i=1 ;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i; sort (q+1 ,q+m+1 ); int pos=1 ; for (re int i=1 ;i<=m;++i){ for (re int j=pos;j<q[i].l;++j){ b.add (lc[a[j]],-1 ); lc[a[j]]=nxt[lc[a[j]]]; b.add (lc[a[j]],1 ); } pos=q[i].l; ans[q[i].id]=b.query (q[i].r); } for (re int i=1 ;i<=m;++i) cout<<ans[i]<<'\n' ; return 0 ; }
采花 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=2e6 +10 ;int n,m,c;int a[maxn],lst[maxn],nxt[maxn],ans[maxn];int lc[maxn]; bitset<maxn> vis;struct query { int l,r,id; inline bool operator < (const query &a)const {return l<a.l;} }q[maxn];struct node { int l,r; }t[maxn];struct BIT { int tr[maxn]; #define lb(x) (x&(-x)) inline void add (int x,int val) {while (x<=n) tr[x]+=val,x+=lb (x);} inline int query (int x) {int res=0 ;while (x) res+=tr[x],x-=lb (x);return res;} #undef lb(x) }b;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>c>>m; for (re int i=1 ;i<=n;++i) cin>>a[i],lst[a[i]]=n+1 ;nxt[n+1 ]=n+1 ; for (re int i=n;i>=1 ;--i) nxt[i]=lst[a[i]],lst[a[i]]=i; for (re int i=1 ;i<=n;++i){ if (!vis[a[i]]){ vis[a[i]]=1 ; lc[a[i]]=nxt[i]; b.add (lc[a[i]],1 ); } } for (re int i=1 ;i<=m;++i) cin>>q[i].l>>q[i].r,q[i].id=i; sort (q+1 ,q+m+1 ); int pos=1 ; for (re int i=1 ;i<=m;++i){ for (re int j=pos;j<q[i].l;++j){ b.add (lc[a[j]],-1 ); lc[a[j]]=nxt[lc[a[j]]]; b.add (lc[a[j]],1 ); } pos=q[i].l; ans[q[i].id]=b.query (q[i].r); } for (re int i=1 ;i<=m;++i) cout<<ans[i]<<'\n' ; return 0 ; }
双倍经验:Product
题意:求
先拆 ,得到
分子分母分开看,上面很好算。
然后算分母。
套路的枚举 。
指数部分太熟了,直接莫反。
设 ,则
可以两次数论分块,复杂度 。
不过复杂度有点劣,能不能再优些?
再看一遍式子:
看到枚举的变量乘在一起了,套路的枚举乘积,则
里面这东西显然可以枚举倍数预处理,外层可以整除分块。
复杂度 。
整除分块里的 是快速幂。
双倍经验被卡空间了,大家还是写欧拉反演吧。
upd:补一下欧拉反演的做法。
从这一步开始
刚才的处理是莫反,这次试试欧拉反演。
因为
所以
然后代回去
把指数整理一下
处理个 的前缀和就没了。
复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e6 +10 ,V=1e6 ,mod=19260817 ;int n,cnt;int pri[maxn],mu[maxn];int inv[maxn];int a[maxn],prod[maxn];int fac[maxn],inv1[maxn],Inv1[maxn];int Fac[maxn]; bitset<maxn> vis;inline int qpow (int a,int b) { int res=1 ; while (b){ if (b&1 ) res=res*a%mod; b>>=1 ; a=a*a%mod; } return res; }inline int Inv (int x) {return qpow (x,mod-2 );}inline void sieve () { mu[1 ]=1 ; for (re int i=2 ;i<=V;++i){ if (!vis[i]) pri[++cnt]=i,mu[i]=-1 ; for (re int j=1 ;j<=cnt&&i*pri[j]<=V;++j){ vis[i*pri[j]]=1 ; if (i%pri[j]==0 ) break ; else mu[i*pri[j]]=-mu[i]; } } }inline void init () { inv[0 ]=inv[1 ]=1 ; for (re int i=2 ;i<=V;++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod; for (re int i=1 ;i<=V;++i) a[i]=1 ; for (re int i=1 ;i<=V;++i){ for (re int j=i;j<=V;j+=i){ if (mu[j/i]==-1 ) a[j]=a[j]*inv[i]%mod; if (mu[j/i]==1 ) a[j]=a[j]*i%mod; } } prod[0 ]=1 ; for (re int i=1 ;i<=V;++i) prod[i]=prod[i-1 ]*a[i]%mod; fac[0 ]=inv1[0 ]=1 ; for (re int i=1 ;i<=V;++i) fac[i]=fac[i-1 ]*prod[i]%mod; Inv1[V]=Inv (fac[V]); for (re int i=V-1 ;i>=1 ;--i) Inv1[i]=Inv1[i+1 ]*prod[i+1 ]%mod; for (re int i=1 ;i<=V;++i) inv1[i]=fac[i-1 ]*Inv1[i]%mod; Fac[0 ]=1 ; for (re int i=1 ;i<=V;++i) Fac[i]=Fac[i-1 ]*i%mod; }inline void solve () { cin>>n; int ans=1 ; for (re int l=1 ,r;l<=n;l=r+1 ){ r=n/(n/l); int res=prod[r]*inv1[l-1 ]%mod; int p=n/l; res=qpow (res,p*p); ans=ans*res%mod; } cout<<qpow (Fac[n],2 *n)%mod*Inv (ans*ans%mod) %mod<<'\n' ; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); sieve ();init (); int T;cin>>T;while (T--) solve (); return 0 ; }
双倍经验:[ABC363G] Dynamic Scheduling
如果把这题弱化一下,那么这是道经典的反悔贪心。
例题:[USACO09OPEN] Work Scheduling G
现在带上了修改,我们考虑用数据结构维护贪心。
首先考虑:如果给定若干个任务必须完成,怎么判断有没有解?
对于一个时间 ,只要必须在 之前完成的任务数量 即可。
所以我们可以类似之前的一道题Koishi Loves Segments ,用线段树维护这个东西。
那么在 加入任务时,我们就给 区间 -1。
现在考虑插入一个任务 。
如果 的最小值 ,那么显然可以直接把这个任务加进去。
否则,我们贪心的考虑:拿这个新任务换掉之前的一个价值较低的任务。
那么这个新任务能替换哪些旧任务呢?
我们找到 中第一个值为 的时刻 ,那么说明 时刻及以前已经被占满了。
我们找到截止时间在 内的价值最小的任务然后尝试替换。
到这里可能会有疑问:为什么是在 之间找最小的替换?新任务的截止时间不是 吗?万一放进去不合法怎么办?
题目给的是任务的截止时间,而不是完成时间。也就是说,对于一个截止时间为 的任务,我们只需要随便找一个 的时刻分配给它即可。
那万一要是 里的任务的截止时间都是 怎么办?你这样不是放不进去了吗?
这显然不可能。
因为如果截止时间都是 ,那么你找到的 就会等于 ,而不是 。
所以说,在 内,一定存在一种分配方案,使得你把旧任务删掉,然后把这个新任务插进去合法。
对于维护 内的用过的价值最小的任务,可以再开一颗线段树。
然后考虑删除一个任务 。
如果这个任务没在最优方案里,那就直接删掉。
否则,把这个任务删掉,然后我们贪心的考虑:拿一个价值最大的任务补上来。
那么哪些任务能补上来呢?
我们找到 中最后一个值为 的时刻 (不存在则为 ),那么说明 时刻及以前已经被占满了。
所以截止时间在 内的没用过的任务都可以用。
我们选最大的那个加入即可。
对于维护 内的没用过的价值最大的任务,可以再开一颗线段树。
因为任务的截止时间可能一样,所以后两颗线段树上要维护 multiset。
复杂度 。
代码看着长,但想清楚了并不难写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 #include "bits/stdc++.h" #define re register #define int long long #define ls(p) tr[p].ls #define rs(p) tr[p].rs using namespace std;const int maxn=3e5 +10 ,inf=1e18 ;int n,m,ans;struct node1 { int l,r,sum; inline node1 operator + (const node1 &a)const { if (sum==a.sum) return {l,a.r,sum}; else if (sum<a.sum) return {l,r,sum}; else return a; } };struct node2 { int t,p; inline node2 operator + (const node2 &a)const { if (p<=a.p) return {t,p}; else return a; } };struct node3 { int t,p; inline node3 operator + (const node3 &a)const { if (p>a.p) return {t,p}; else return a; } };namespace tree1{ int rt,segcnt; struct tree { int ls,rs; node1 s; int tag; }tr[maxn<<1 ]; inline void up (int p) {tr[p].s=tr[ls (p)].s+tr[rs (p)].s;} inline void add (int p,int val) {tr[p].s.sum+=val,tr[p].tag+=val;} inline void down (int p) {if (tr[p].tag) add (ls (p),tr[p].tag),add (rs (p),tr[p].tag);tr[p].tag=0 ;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){tr[p].s={l,l,l};return ;} int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); } inline void update (int l,int r,int L,int R,int val,int p) { if (l>=L&&r<=R){add (p,val);return ;} int mid=(l+r)>>1 ;down (p); if (L<=mid) update (l,mid,L,R,val,ls (p)); if (R>mid) update (mid+1 ,r,L,R,val,rs (p)); up (p); } inline node1 query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return tr[p].s; int mid=(l+r)>>1 ;down (p); if (R<=mid) return query (l,mid,L,R,ls (p)); if (L>mid) return query (mid+1 ,r,L,R,rs (p)); return query (l,mid,L,R,ls (p))+query (mid+1 ,r,L,R,rs (p)); } }namespace tree2{ int rt,segcnt; struct tree { int ls,rs; multiset<int > s; node2 mn; }tr[maxn<<1 ]; inline void up (int p) {tr[p].mn=tr[ls (p)].mn+tr[rs (p)].mn;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){tr[p].mn={l,inf};return ;} int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); } inline void add (int l,int r,int pos,int val,int p) { if (l==r){ tr[p].s.insert (val); tr[p].mn.p=min (tr[p].mn.p,val); return ; } int mid=(l+r)>>1 ; if (pos<=mid) add (l,mid,pos,val,ls (p)); else add (mid+1 ,r,pos,val,rs (p)); up (p); } inline void del (int l,int r,int pos,int val,int p) { if (l==r){ tr[p].s.erase (tr[p].s.find (val)); if (tr[p].s.empty ()) tr[p].mn.p=inf; else tr[p].mn.p=*tr[p].s.begin (); return ; } int mid=(l+r)>>1 ; if (pos<=mid) del (l,mid,pos,val,ls (p)); else del (mid+1 ,r,pos,val,rs (p)); up (p); } inline bool find (int l,int r,int pos,int val,int p) { if (l==r) return tr[p].s.count (val); int mid=(l+r)>>1 ; if (pos<=mid) return find (l,mid,pos,val,ls (p)); else return find (mid+1 ,r,pos,val,rs (p)); } inline node2 query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return tr[p].mn; int mid=(l+r)>>1 ; if (R<=mid) return query (l,mid,L,R,ls (p)); if (L>mid) return query (mid+1 ,r,L,R,rs (p)); return query (l,mid,L,R,ls (p))+query (mid+1 ,r,L,R,rs (p)); } }namespace tree3{ int rt,segcnt; struct tree { int ls,rs; multiset<int ,greater<int >> s; node3 mx; }tr[maxn<<1 ]; inline void up (int p) {tr[p].mx=tr[ls (p)].mx+tr[rs (p)].mx;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){tr[p].mx={l,-inf};return ;} int mid=(l+r)>>1 ; build (l,mid,ls (p)),build (mid+1 ,r,rs (p)); up (p); } inline void add (int l,int r,int pos,int val,int p) { if (l==r){ tr[p].s.insert (val); tr[p].mx.p=max (tr[p].mx.p,val); return ; } int mid=(l+r)>>1 ; if (pos<=mid) add (l,mid,pos,val,ls (p)); else add (mid+1 ,r,pos,val,rs (p)); up (p); } inline void del (int l,int r,int pos,int val,int p) { if (l==r){ tr[p].s.erase (tr[p].s.find (val)); if (tr[p].s.empty ()) tr[p].mx.p=-inf; else tr[p].mx.p=*tr[p].s.begin (); return ; } int mid=(l+r)>>1 ; if (pos<=mid) del (l,mid,pos,val,ls (p)); else del (mid+1 ,r,pos,val,rs (p)); up (p); } inline node3 query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return tr[p].mx; int mid=(l+r)>>1 ; if (R<=mid) return query (l,mid,L,R,ls (p)); if (L>mid) return query (mid+1 ,r,L,R,rs (p)); return query (l,mid,L,R,ls (p))+query (mid+1 ,r,L,R,rs (p)); } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m;string c; tree1::build (1 ,n,tree1::rt),tree2::build (1 ,n,tree2::rt),tree3::build (1 ,n,tree3::rt); for (re int i=1 ,t,p;i<=m;++i){ cin>>c>>t>>p; if (c=="ADD" ){ node1 res=tree1::query (1 ,n,t,n,1 ); if (res.sum>0 ){ ans+=p; tree1::update (1 ,n,t,n,-1 ,1 ); tree2::add (1 ,n,t,p,1 ); } else { node2 tmp=tree2::query (1 ,n,1 ,res.l,1 ); if (tmp.p>p) tree3::add (1 ,n,t,p,1 ); else { ans+=p-tmp.p; tree1::update (1 ,n,tmp.t,n,1 ,1 ); tree2::del (1 ,n,tmp.t,tmp.p,1 ); tree3::add (1 ,n,tmp.t,tmp.p,1 ); tree1::update (1 ,n,t,n,-1 ,1 ); tree2::add (1 ,n,t,p,1 ); } } } else { if (!tree2::find (1 ,n,t,p,1 )) tree3::del (1 ,n,t,p,1 ); else { ans-=p; tree1::update (1 ,n,t,n,1 ,1 ); tree2::del (1 ,n,t,p,1 ); node1 res=tree1::query (1 ,n,1 ,n,1 ); int pos=(res.sum<=0 ?res.r:0 ); if (pos<n){ node3 tmp=tree3::query (1 ,n,pos+1 ,n,1 ); if (tmp.p!=-inf){ ans+=tmp.p; tree1::update (1 ,n,tmp.t,n,-1 ,1 ); tree2::add (1 ,n,tmp.t,tmp.p,1 ); tree3::del (1 ,n,tmp.t,tmp.p,1 ); } } } } cout<<ans<<'\n' ; } return 0 ; }
本题有点分树做法和树剖主席树做法。
我写了点分树做法,树剖主席树可以看之前的一篇总结 。
如果没有年龄限制,怎么做?
先建出点分树,对每个点维护 表示点分树上 子树所有点到 的距离和, 表示点分树上 子树所有点到 的点分树上父亲的距离和。
设查询点为 ,当前跳到点 。
查询时,在点分树上跳父亲,且跳的时候加上
现在有了年龄限制,那么在存信息的时候多开一维,把年龄限制也算上。
然后按年龄排序,做个前缀和,查询时只需要差分一下即可。
代码是很久以前写的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 #include "bits/stdc++.h" #define int long long #define re register using namespace std;const int maxn=2e5 +10 ;int n,m,Q,cnt,num,rt,lst,s1,s2;int a[maxn],head[maxn],siz[maxn],mx[maxn],dfa[maxn]; bitset<maxn> vis;struct node { int fi,se; bool operator < (const node &a) const { return fi<a.fi; } }; vector<node> sum1[maxn],sum2[maxn];struct edge { int to,nxt,w; }e[maxn<<1 ];struct node1 { int fa,dep,siz,top,hson,dis; }t[maxn];inline void add (int u,int v,int w) { e[++cnt]={v,head[u],w}; head[u]=cnt; }inline void dfs1 (int u,int fa) { t[u].fa=fa; t[u].siz=1 ; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==fa) continue ; t[v].dep=t[u].dep+1 ; t[v].dis=t[u].dis+e[i].w; dfs1 (v,u); t[u].siz+=t[v].siz; if (!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v; } }inline void dfs2 (int u,int top) { t[u].top=top; if (!t[u].hson) return ; dfs2 (t[u].hson,top); for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (v==t[u].fa||v==t[u].hson) continue ; dfs2 (v,v); } }inline int lca (int u,int v) { while (t[u].top!=t[v].top){ if (t[t[u].top].dep<t[t[v].top].dep) swap (u,v); u=t[t[u].top].fa; } return (t[u].dep<t[v].dep?u:v); }inline int getdis (int u,int v) {return t[u].dis+t[v].dis-(t[lca (u,v)].dis<<1 );}inline void getrt (int u,int fa) { siz[u]=1 ,mx[u]=0 ; for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (vis[v]||v==fa) continue ; getrt (v,u); siz[u]+=siz[v],mx[u]=max (mx[u],siz[v]); } mx[u]=max (mx[u],num-siz[u]); if (mx[u]<mx[rt]||!rt) rt=u; }inline void dfs3 (int u,int fa,int dep) { sum1[rt].push_back ({a[u],dep}); if (dfa[rt]) sum2[rt].push_back ({a[u],getdis (u,dfa[rt])}); for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (vis[v]||v==fa) continue ; dfs3 (v,u,dep+e[i].w); } }inline void dfs (int u) { vis[u]=1 ;dfs3 (u,0 ,0 ); for (re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if (vis[v]) continue ; num=siz[v],rt=0 ; getrt (v,u); dfa[rt]=u; dfs (rt); } }inline void build () { num=n,rt=0 ; getrt (1 ,0 ); dfs (rt); }inline int query1 (int u,int l,int r) { int L=lower_bound (sum1[u].begin (),sum1[u].end (),node{l,0 })-sum1[u].begin ()-1 ; int R=upper_bound (sum1[u].begin (),sum1[u].end (),node{r,0 })-sum1[u].begin ()-1 ; s1=R-L; int res=0 ; if (L>=0 &&L<sum1[u].size ()) res-=sum1[u][L].se; if (R>=0 &&R<sum1[u].size ()) res+=sum1[u][R].se; return res; }inline int query2 (int u,int l,int r) { int L=lower_bound (sum2[u].begin (),sum2[u].end (),node{l,0 })-sum2[u].begin ()-1 ; int R=upper_bound (sum2[u].begin (),sum2[u].end (),node{r,0 })-sum2[u].begin ()-1 ; s2=R-L; int res=0 ; if (L>=0 &&L<sum2[u].size ()) res-=sum2[u][L].se; if (R>=0 &&R<sum2[u].size ()) res+=sum2[u][R].se; return res; }inline int query (int u,int l,int r) { int now=u,res=query1 (u,l,r); while (now){ res-=query2 (now,l,r); if (dfa[now]) res+=query1 (dfa[now],l,r),res+=(s1-s2)*getdis (u,dfa[now]); now=dfa[now]; } return res; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>Q>>m; for (re int i=1 ;i<=n;++i) cin>>a[i]; for (re int i=1 ,u,v,w;i<n;++i) cin>>u>>v>>w,add (u,v,w),add (v,u,w); dfs1 (1 ,0 ),dfs2 (1 ,1 ),build (); for (re int i=1 ;i<=n;++i){ sort (sum1[i].begin (),sum1[i].end ()); sort (sum2[i].begin (),sum2[i].end ()); for (re int j=1 ;j<sum1[i].size ();++j) sum1[i][j].se+=sum1[i][j-1 ].se; for (re int j=1 ;j<sum2[i].size ();++j) sum2[i][j].se+=sum2[i][j-1 ].se; } while (Q--){ int u,l,r,a,b; cin>>u>>a>>b; l=min ((a+lst)%m,(b+lst)%m); r=max ((a+lst)%m,(b+lst)%m); cout<<(lst=query (u,l,r))<<'\n' ; } return 0 ; }
写了维护平方和草过去了。
正解还是看题解吧,我不太喜欢做这种哈希题,感觉一直不太理解?
模板题。
想要详细学习推荐ix35巨佬的博客 。
我的第一道曼哈顿距离转切比雪夫距离,差点独立切掉。
以下是我的思路:
曼哈顿距离不好做,考虑转化成切比雪夫距离。
然后对于计算答案,我们考虑钦定在编号大的点计算。
转化为切比雪夫距离后, 和 显然独立,所以下面只考虑一种。
那么问题变为:求 。
把绝对值拆开,发现只需要找大于 的最大值和小于 的最小值即可。
现在已经可以支持前两个操作了,考虑操作三。
因为要去掉一个,所以得记录下来最优解点对 。
如果去掉 ,那比较好办,我们对于 维护次优解即可。
但去掉 呢?
不会了。
以下是正解:
对于最优解点对 ,设它产生的价值为 ,那么我们把区间 都和 取 。
那么查询时只需要单点查即可。
也就是说,我们维护一颗线段树,位置 表示去掉点 时的最大价值。
那么线段树要维护的操作为:区间最值,单点查询。
可以用吉司机线段树,不过好像有简单做法。
upd:直接标记永久化就好了啊。
代码这样写就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 inline void update (int l,int r,int L,int R,int val,int p) { if (L>R) return ; if (l>=L&&r<=R){tr[p].mx=max (tr[p].mx,val);return ;} int mid=(l+r)>>1 ; if (L<=mid) update (l,mid,L,R,val,ls (p)); if (R>mid) update (mid+1 ,r,L,R,val,rs (p)); }inline int query (int l,int r,int pos,int p) { if (l==r) return tr[p].mx; int mid=(l+r)>>1 ; if (pos<=mid) return max (tr[p].mx,query (l,mid,pos,ls (p))); else return max (tr[p].mx,query (mid+1 ,r,pos,rs (p))); }
然后具体说一下怎么维护之前那个式子。
一开始我还想上数据结构,结果发现根本不用。
可以发现,对于一个新加入的点 ,能让 取到最大的 一定是 的最大值或最小值,所以直接维护最大值和最小值即可。
但因为我们有操作三,所以还需要维护次大值和次小值。
别忘了还有 ,跟上面说的一堆的处理完全相同。
原题:BZOJ3514
绷,原题是以前讲课题,当时讲课的时候好像就没想出来。
我会离线!
如果能想到莫队,那基本上就做完了。
加边好做删边困难,容易想到回滚莫队。
那怎么维护答案?并查集!
因为要支持撤销,所以要用可撤销并查集。
然而这题强制在线,所以上面说的并没有什么用。
好久没碰过 LCT 了,对 LCT 不太熟悉了啊。
首先有结论:无环图的连通块个数等于点数-边数。
所以我们考虑维护原图的生成树森林。
考虑用 LCT 维护以时间为边权的最大生成树。加入一条边时,如果会形成环,那就找到最早的一条边断掉。
设一条边被删除的时间为 ,那么查询的答案为 。
因为对于 的一条边,它连上后一定会连接两个连通块,使得答案 -1。
数组 可以在 LCT 维护过程中都求出来,然后就是这个区间查询:区间查小于 的数的个数,强制在线。
这二维数点怎么做都行吧,但大部分人好像都写了主席树。
题意不明题。
以下的 为原题中的 。
首先把期望用定义拆开,那么就要求概率和价值。
概率转化为计数:受伤的方案数除以总方案数。
连线不经过任何其他格点,这是经典结论:设两点坐标分别为 ,则不经过任何其他格点的充要条件为 。
但这里题目没有说清楚,按理来说在同一行和同一列的情况是经过了其他格点的,但是也得算上。
设 。
对于一对 ,合法的方案数为 (因为点对有序,所以分四个方向分别算)。
所以受伤方案数为
后面这东西莫反直接写了
显然可以整除分块。
所以受伤概率为
显然每回合受伤概率相同,设概率为 ,则期望伤害为
复杂度 ,瓶颈在预处理。
矩阵树定理+容斥原理好题。
刚学矩阵树的时候就做过这道题。
要求每条边颜色都不相同的生成树数量,直接求是不好求的。
但 很小,所以可以考虑容斥。
我们暴力枚举用哪些颜色的边,然后矩阵树定理求出这种情况下的生成树数量,然后再乘上相应的容斥系数即可。
复杂度
厉害结论题,我被秒杀了。
结论:对于一张左部点和右部点点数相同的二分图,其有且仅有一个完美匹配的必要条件为存在一个节点的入度为 。
证明:考虑反证法。
假设每个点的入度都大于等于 。
我们用 表示左部点中的第 个点, 表示右部点中的第 个点。
我们把该图的唯一完美匹配的所有边都删掉,然后在剩下的边中,把 的边变为一张 个点的有向图中的边 。
那么该有向图中每个点都有出边,必然存在环 。
则在原二分图中 。
我们把原匹配中的边 换成 即可得到一个新的匹配。
实现方面,我们可以对这张二分图“拓扑排序”。
每次找到入度为 的点 入队,然后找到和 相连的点 ,把点 删掉。
然后找到所有活着的和点 连着的点,把它们的入度 -1。
若最后删除的点的数量为 ,则说明合法。
终于鼓起勇气写这道题了。
如果你见的套路还不够多,那么建议先去写点别的简单莫反再来挑战这道题。
先拆 :
那么只需要处理 和 即可。
type = 0 算分子部分:
这东西很好算吧,易得为:
然后是分母:
这不是天守阁的地板 吗?
套路枚举 ,得
指数部分经典莫反,直接写了
看到枚举的变量乘在一起了,考虑枚举 ,得
里面这东西显然可以枚举倍数做到 预处理,剩下的整除分块可做到 。
type = 1 要稍微上难度了。
算分子部分:
把 放到指数上变为求和,得
设 ,则
里面 预处理即可。
然后是分母:
先把 放指数上,得
看到里面这东西,直接算肯定算不了,于是依旧考虑枚举 :
指数部分就是[国家集训队] Crash的数字表格 / JZPTAB 。
但我有点忘了,为了防止以后不会,下面把它推的过程详细写出来。
把枚举 换为枚举 ,得(下面的 是原来枚举的 )
把枚举 换为枚举 ,得(下面的 是原来枚举的 )
现在可以代回去了:
看到枚举的变量乘在一起了,考虑枚举 ,得
里面这东西显然可以枚举倍数+快速幂做到 预处理,剩下的整除分块可做到 。
type = 2 困难的来了。
算分子部分:
依旧枚举 ,得
枚举 ,得
看到枚举的变量乘在一起了,考虑枚举 ,得
发现只有指数上有 ,把 放到指数上变成 ,得
发现指数是个经典的狄利克雷卷积,用 换掉:
预处理 的前缀积和 的前缀和,复杂度 。
剩下的整除分块 。
然后是分母:
已经不太能自己推出来了,参考了题解。
套路的考虑枚举 ,指数部分根据 的结合律会变为:
里面的指数是标准莫反,外面的指数是标准的欧拉反演。
把外面的再写一下。欧拉反演我用的不是很多,也不太熟练。
代到原式:
看到枚举的变量乘在一起了,考虑枚举 ,得
要同时枚举 ,没法化了。
然后题解告诉我们,把 拆成 和 分别算,然后再乘起来。
那么先看第一部分:
设 。把 提到最前面枚举,得
把枚举 换为枚举 ,得(下面的 是原来枚举的 )
发现只有指数上有 ,把 放到指数上变成 ,得
发现指数是个经典的狄利克雷卷积,用 换掉:
是个和分子部分一样的形式,同样处理即可。
然后看第二部分:
依旧把 提到最前面枚举,得
把枚举 换为枚举 ,得(下面的 是原来枚举的 )
把枚举 换为枚举 ,得(下面的 是原来枚举的 )
最里面的东西在之前已经处理过了,然后可以整除分块套整除分块做到 的复杂度。
问题得以解决。
听说比较卡常,所以代码有空再写。
upd:好像不太卡常啊,但我取模取少了导致一直死···
一些注意事项:
减法记得加上模数,千万别出负数。
根据欧拉定理,指数应该对 取模,而不是 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 #include "bits/stdc++.h" #define re register #define int long long #define min(a,b) (a<b?a:b) using namespace std;const int maxn=1e5 +10 ,V=1e5 ;int mod,_mod,A,B,C,cnt;int fac[maxn],inv[maxn];int fac1[maxn];int fac2[maxn],inv_fac2[maxn];int a[maxn],prod[maxn],inv_prod[maxn];int b[maxn],prod1[maxn],inv_prod1[maxn];int tmp_prod[maxn],tmp_inv[maxn];int pri[maxn];int mu[maxn],phi[maxn];int s_phi[maxn]; bitset<maxn> vis;inline int qpow (int a,int b) { a%=mod,b%=_mod; int res=1 ; while (b){ if (b&1 ) res=res*a%mod; b>>=1 ; a=a*a%mod; } return res; }inline int Inv (int x) {return qpow (x,mod-2 );}inline int F (int x) {return (x*(x+1 )/2 )%_mod;}inline void sieve () { mu[1 ]=1 ,phi[1 ]=1 ; for (re int i=2 ;i<=V;++i){ if (!vis[i]) pri[++cnt]=i,mu[i]=-1 ,phi[i]=i-1 ; for (re int j=1 ;j<=cnt&&i*pri[j]<=V;++j){ vis[i*pri[j]]=1 ; if (i%pri[j]==0 ){phi[i*pri[j]]=phi[i]*pri[j];break ;} else mu[i*pri[j]]=-mu[i],phi[i*pri[j]]=phi[i]*phi[pri[j]]; } } for (re int i=1 ;i<=V;++i) s_phi[i]=(s_phi[i-1 ]+phi[i])%_mod; }inline void pre_fac () { inv[0 ]=inv[1 ]=1 ; for (re int i=2 ;i<=V;++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod; fac[0 ]=1 ; for (re int i=1 ;i<=V;++i) fac[i]=fac[i-1 ]*i%mod; fac1[0 ]=1 ; for (re int i=1 ;i<=V;++i) fac1[i]=fac1[i-1 ]*qpow (i,i)%mod; fac2[0 ]=1 ; for (re int i=1 ;i<=V;++i) fac2[i]=fac2[i-1 ]*qpow (i,phi[i])%mod; tmp_prod[0 ]=inv_fac2[0 ]=1 ; for (re int i=1 ;i<=V;++i) tmp_prod[i]=tmp_prod[i-1 ]*fac2[i]%mod; tmp_inv[V]=Inv (tmp_prod[V]); for (re int i=V-1 ;i>=1 ;--i) tmp_inv[i]=tmp_inv[i+1 ]*fac2[i+1 ]%mod; for (re int i=1 ;i<=V;++i) inv_fac2[i]=tmp_prod[i-1 ]*tmp_inv[i]%mod; }inline void pre_f1 () { for (re int i=1 ;i<=V;++i) a[i]=1 ; for (re int d=1 ;d<=V;++d){ for (re int t=d;t<=V;t+=d){ if (mu[t/d]==-1 ) a[t]=a[t]*inv[d]%mod; if (mu[t/d]==1 ) a[t]=a[t]*d%mod; } } prod[0 ]=1 ; for (re int i=1 ;i<=V;++i) prod[i]=prod[i-1 ]*a[i]%mod; tmp_prod[0 ]=inv_prod[0 ]=1 ; for (re int i=1 ;i<=V;++i) tmp_prod[i]=tmp_prod[i-1 ]*prod[i]%mod; tmp_inv[V]=Inv (tmp_prod[V]); for (re int i=V-1 ;i>=1 ;--i) tmp_inv[i]=tmp_inv[i+1 ]*prod[i+1 ]%mod; for (re int i=1 ;i<=V;++i) inv_prod[i]=tmp_prod[i-1 ]*tmp_inv[i]%mod; }inline void pre_f2 () { for (re int i=1 ;i<=V;++i) b[i]=1 ; for (re int d=1 ;d<=V;++d){ for (re int t=d;t<=V;t+=d){ if (mu[t/d]==-1 ) b[t]=b[t]*Inv (qpow (d,t*t))%mod; if (mu[t/d]==1 ) b[t]=b[t]*qpow (d,t*t)%mod; } } prod1[0 ]=1 ; for (re int i=1 ;i<=V;++i) prod1[i]=prod1[i-1 ]*b[i]%mod; tmp_prod[0 ]=inv_prod1[0 ]=1 ; for (re int i=1 ;i<=V;++i) tmp_prod[i]=tmp_prod[i-1 ]*prod1[i]%mod; tmp_inv[V]=Inv (tmp_prod[V]); for (re int i=V-1 ;i>=1 ;--i) tmp_inv[i]=tmp_inv[i+1 ]*prod1[i+1 ]%mod; for (re int i=1 ;i<=V;++i) inv_prod1[i]=tmp_prod[i-1 ]*tmp_inv[i]%mod; }inline void init () { sieve (); pre_fac (); pre_f1 (); pre_f2 (); }inline int _f2(int a,int b){ int res=1 ,lim=min (a,b); for (re int l=1 ,r;l<=lim;l=r+1 ){ r=min (a/(a/l),b/(b/l));if (r>lim) r=lim; res=res*qpow (prod[r]*inv_prod[l-1 ],(a/l)*(b/l))%mod; } return res; }inline int f1 (int a,int b,int c,int type) { if (type==0 ) return qpow (fac[a],b*c); if (type==1 ) return qpow (fac1[a],F (b)*F (c)); if (type==2 ){ int res=1 ,lim=min (min (a,b),c); for (re int l=1 ,r;l<=lim;l=r+1 ){ r=min (min (a/(a/l),b/(b/l)),c/(c/l));if (r>lim) r=lim; res=res*qpow (fac2[r]*inv_fac2[l-1 ],(a/l)*(b/l)%_mod*(c/l))%mod; res=res*qpow (fac[a/l],(b/l)*(c/l)%_mod*(s_phi[r]-s_phi[l-1 ]+_mod)%_mod)%mod; } return res; } }inline int f2 (int a,int b,int c,int type) { int res=1 ; if (type==0 ){ int lim=min (a,b); for (re int l=1 ,r;l<=lim;l=r+1 ){ r=min (a/(a/l),b/(b/l));if (r>lim) r=lim; res=res*qpow (prod[r]*inv_prod[l-1 ],(a/l)*(b/l))%mod; } res=qpow (res,c); } if (type==1 ){ int lim=min (a,b); for (re int l=1 ,r;l<=lim;l=r+1 ){ r=min (a/(a/l),b/(b/l));if (r>lim) r=lim; res=res*qpow (prod1[r]*inv_prod1[l-1 ],F ((a/l))*F ((b/l)))%mod; } res=qpow (res,F (c)); } if (type==2 ){ int lim=min (min (a,b),c); for (re int l=1 ,r;l<=lim;l=r+1 ){ r=min (min (a/(a/l),b/(b/l)),c/(c/l));if (r>lim) r=lim; res=res*qpow (fac2[r]*inv_fac2[l-1 ],(a/l)*(b/l)%_mod*(c/l))%mod; res=res*qpow (_f2(a/l,b/l),(s_phi[r]-s_phi[l-1 ]+_mod)%_mod*(c/l))%mod; } } return res; }inline void solve () { cin>>A>>B>>C; for (re int type=0 ;type<=2 ;++type) cout<<f1 (A,B,C,type)%mod*f1 (B,A,C,type) %mod*Inv (f2(A,B,C,type)) %mod*Inv (f2(A,C,B,type)) %mod<<' ' ; cout<<'\n' ; }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); int T;cin>>T>>mod;_mod=mod-1 ;init ();while (T--) solve (); cerr<<1.0 *clock ()/CLOCKS_PER_SEC; return 0 ; }
感觉和[Cnoi2019] 须臾幻境 的离线版很像。
可以发现答案为:。
区间加边可以套用[Cnoi2019] 须臾幻境 那题的做法:回滚莫队+可撤销并查集。
指针移动时维护每个连通块的权值和,然后更新一下答案。
感觉不太会写回滚莫队啊?
upd:竟然在不太会写回滚莫队的情况下一遍过了,基本没调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include "bits/stdc++.h" #define re register #define int long long #define pii pair<int,int> #define fi first #define se second using namespace std;const int maxn=1e5 +10 ,maxm=500 ,mod=1e9 +7 ;int n,m,Q,siz,tot,bs;int a[maxn],ans[maxn];int st[maxm],ed[maxm],bel[maxn<<1 ];struct edge { int u,v; }e[maxn<<1 ];struct query { int l,r,id; }q[maxn];struct dsu { int fa[maxn],siz[maxn],w[maxn]; stack<pii> stk; inline int find (int x) {if (x!=fa[x]) return find (fa[x]);return fa[x];} inline void merge (int u,int v,int &now,bool type) { int x=find (u),y=find (v); if (x==y){if (type) stk.push ({0 ,0 });return ;} if (siz[x]<siz[y]) swap (x,y); if (type) stk.push ({x,y}); now=((now-w[x]*w[x]%mod-w[y]*w[y]%mod)%mod+mod)%mod; siz[x]+=siz[y],fa[y]=x,w[x]=(w[x]+w[y])%mod; now=(now+w[x]*w[x]%mod)%mod; } inline void undo () { int x=stk.top ().fi,fi,y=stk.top ().se;stk.pop (); fa[y]=y,siz[x]-=siz[y],w[x]=((w[x]-w[y])%mod+mod)%mod; } }t1,t2;inline bool cmp (query a,query b) { if (bel[a.l]==bel[b.l]) return a.r<b.r; return bel[a.l]<bel[b.l]; }inline int qpow (int a,int b) { int res=1 ; while (b){ if (b&1 ) res=res*a%mod; b>>=1 ; a=a*a%mod; } return res; }inline int Inv (int x) {return qpow (x,mod-2 );}inline void init () { siz=sqrt (m); tot=m/siz; for (re int i=1 ;i<=tot;++i) st[i]=(i-1 )*siz+1 ,ed[i]=i*siz; if (ed[tot]<m) ++tot,st[tot]=ed[tot-1 ]+1 ,ed[tot]=m; for (re int i=1 ;i<=tot;++i) for (re int j=st[i];j<=ed[i];++j) bel[j]=i; }inline void solve (int l,int r,int id) { int res=bs; for (re int i=l;i<=r;++i) t2. merge (e[i].u,e[i].v,res,1 ); ans[id]=((res-bs)%mod+mod)%mod; for (re int i=l;i<=r;++i) t2. undo (); }inline void add (int id,int &res,bool type) {t1. merge (e[id].u,e[id].v,res,type);}signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>Q; for (re int i=1 ;i<=n;++i) cin>>a[i],bs=(bs+a[i]*a[i])%mod; for (re int i=1 ;i<=n;++i) t1.f a[i]=i,t1. siz[i]=1 ,t1. w[i]=a[i]; for (re int i=1 ;i<=n;++i) t2.f a[i]=i,t2. siz[i]=1 ,t2. w[i]=a[i]; for (re int i=1 ;i<=m;++i) cin>>e[i].u>>e[i].v; for (re int i=1 ;i<=Q;++i) cin>>q[i].l>>q[i].r,q[i].id=i; init (); sort (q+1 ,q+Q+1 ,cmp); int l=1 ,r=0 ,lst=0 ,now=bs; for (re int i=1 ;i<=Q;++i){ if (bel[q[i].l]==bel[q[i].r]) solve (q[i].l,q[i].r,q[i].id); else { if (lst^bel[q[i].l]){ for (re int i=1 ;i<=n;++i) t1.f a[i]=i,t1. siz[i]=1 ,t1. w[i]=a[i]; l=ed[bel[q[i].l]]+1 ,r=ed[bel[q[i].l]]; now=bs,lst=bel[q[i].l]; } while (r<q[i].r) add (++r,now,0 ); int tmp=now,l1=l; while (l1>q[i].l) add (--l1,tmp,1 ); ans[q[i].id]=((tmp-bs)%mod+mod)%mod; while (l1<l) l1++,t1. undo (); } } for (re int i=1 ;i<=Q;++i) cout<<ans[i]*Inv (n*(n-1 )%mod)%mod<<'\n' ; return 0 ; }
思路又错了···
我的思路:
首先考虑全局询问怎么做。
一种显然的想法是枚举 ,然后找前面的最大值和后面的最大值。
现在变为区间询问还带修,考虑线段树维护。
我们对一个区间维护它的最优决策点,然后考虑合并两个区间。
然后就没有然后了···
因为仔细思考一下,会发现有一堆问题。
正解思路:
我们不枚举 ,而是考虑 。
考虑怎么合并两个区间。
如果 都在左/右儿子,那直接继承答案。
如果 在左儿子, 在右儿子,那么考虑 在哪。
如果 在左儿子,那么需要得到左儿子的 和右儿子的 。
如果 在右儿子,那么需要得到左儿子的 和右儿子的 。
然后考虑怎么维护 。
依旧考虑合并。
若 都在左/右儿子,那直接继承答案。
若 在左儿子, 在右儿子,那么需要得到左儿子的 和右儿子的 。
另一个维护同理。
这次思路对了,想到了扫描线,然后不会做了哈哈。
事实上第一眼想的是莫队,然后发现不好删除,于是考虑回滚莫队。
但看到标签是线段树所以没细想,事后在题解区发现大力卡常还真能过。
正解思路:
我们将询问离线扫描线,假设当前扫到的右端点为 。
因为每种颜色独立,我们只把其中一种拿出来看。
可以发现,当右端点 固定时,左端点向左扫,某个元素会成为最小值所对应的左端点是一段区间。
所以我们把每个位置能成为最小值的区间找出来,处理区间可以用单调栈。
然后扫描线。
当扫到一个点时,把它加进去;当前扫到的右端点已经不在这个点的区间里时,把它扔掉。
然后可以在线段树上每个节点维护一个可删堆,修改时标记永久化。
做了这么久题还是只会 板子难度的题,唉。
首先考虑没有单点修改怎么做。
我们维护以下信息:
序列 操作后的状态。
原位置 现在的位置,记作 。
现位置 原来的位置,记作 。
现位置 的贡献次数,记作 。
然后考虑指针移动带来的影响。
当右指针右移时:
若操作为交换 ,则交换 和 , 和 , 和 , 和 。
若操作为查询 ,则 ,。
当左指针左移时:
因为在实际的操作时,这个操作是最先进行的,所以这个操作会对后面的操作都有影响。
因为这个操作是最先进行的,所以它操作的位置都是最初始的位置,对应到当前的序列上即为 。
若操作为查询 ,则 ,。
若操作为交换 ,则交换 和 , 和 , 和 。因为交换操作对后面的查询都有影响,所以还要考虑对答案的影响,则 。
缩小区间的指针移动同理。
然后考虑带上单点修改怎么做。
我们可以新建一堆点,然后把单点修改转化为交换操作。
我对这道题的理解也就仅限能理解了,再遇到这种题估计还是没思路···
代码贺的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include "bits/stdc++.h" #define re register #define uint unsigned int using namespace std;const int maxn=4e5 +10 ,maxm=2e5 +10 ;int n,m,Q,siz; uint now,a[maxn],ans[maxm],pos[maxn],rev[maxn],num[maxn];struct change { int op,x,y; }t[maxm];struct query { int l,r,id; }q[maxm];inline bool cmp (query a,query b) {return ((a.l/siz)^(b.l/siz))?(a.l/siz)<(b.l/siz):((a.l/siz)&1 )?a.r<b.r:a.r>b.r;}inline void add1 (int id) { if (t[id].op==2 ){ swap (a[t[id].x],a[t[id].y]); swap (rev[t[id].x],rev[t[id].y]); swap (num[t[id].x],num[t[id].y]); swap (pos[rev[t[id].x]],pos[rev[t[id].y]]); } else { now+=a[t[id].x]; ++num[t[id].x]; } }inline void add2 (int id) { if (t[id].op==2 ){ swap (pos[t[id].x],pos[t[id].y]); swap (a[pos[t[id].x]],a[pos[t[id].y]]); swap (rev[pos[t[id].x]],rev[pos[t[id].y]]); now+=(a[pos[t[id].x]]-a[pos[t[id].y]])*(num[pos[t[id].x]]-num[pos[t[id].y]]); } else { now+=a[pos[t[id].x]]; ++num[pos[t[id].x]]; } }inline void del1 (int id) { if (t[id].op==2 ){ swap (a[t[id].x],a[t[id].y]); swap (rev[t[id].x],rev[t[id].y]); swap (num[t[id].x],num[t[id].y]); swap (pos[rev[t[id].x]],pos[rev[t[id].y]]); } else { now-=a[t[id].x]; --num[t[id].x]; } }inline void del2 (int id) { if (t[id].op==2 ){ now-=(a[pos[t[id].x]]-a[pos[t[id].y]])*(num[pos[t[id].x]]-num[pos[t[id].y]]); swap (pos[t[id].x],pos[t[id].y]); swap (a[pos[t[id].x]],a[pos[t[id].y]]); swap (rev[pos[t[id].x]],rev[pos[t[id].y]]); } else { now-=a[pos[t[id].x]]; --num[pos[t[id].x]]; } }signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (re int i=1 ;i<=n;++i) cin>>a[i]; for (re int i=1 ,op,x,y;i<=m;++i){ cin>>op>>x; if (op==1 ){y=++n;cin>>a[n];op=2 ;} else if (op==2 ) cin>>y; t[i]={op,x,y}; } for (re int i=1 ;i<=n;++i) pos[i]=rev[i]=i; cin>>Q; for (re int i=1 ;i<=Q;++i) cin>>q[i].l>>q[i].r,q[i].id=i; siz=sqrt (m); sort (q+1 ,q+Q+1 ,cmp); int l=1 ,r=0 ; for (re int i=1 ;i<=Q;++i){ while (r<q[i].r) add1 (++r); while (l>q[i].l) add2 (--l); while (r>q[i].r) del1 (r--); while (l<q[i].l) del2 (l++); ans[q[i].id]=now; } for (re int i=1 ;i<=Q;++i) cout<<ans[i]<<'\n' ; return 0 ; }
不会套路题了不会套路题了不会套路题了不会套路题了不会套路题了
弱化版:[COCI2006-2007#4] ZBRKA
先来看弱化版怎么做。
对于这种排列计数问题,套路的状态设计是设 表示将 填入排列的方案数,后面的维度可以按照所求补充,然后考虑新加入一个数对答案产生的贡献。
那么这道题要求逆序对数,所以设 表示将 填入排列,逆序对数为 的方案数。
转移就是考虑把 放到哪里。因为 比 都要大,所以把 放到 个数前面就会产生 个逆序对,则转移为
发现第二维是一个区间求和,用前缀和优化即可做到 。
然后再看这道题。
概率显然可以转化为计数。设 表示将 填入排列,逆序对数在 意义下 为 的方案数,转移为
然后可以发现,第二维的转移依旧是连续区间,前缀和优化就做完了。
下面是具体的推导:
设 。
当 时,, 都能贡献到 。
当 时,即 ,此时在 后 。
此时的贡献区间变为 和 ,即 , 都能贡献到 。
复杂度 。
题解区还给出了一个结论:当 时,每种情况的概率均为 。
我用了,因为这样就不用写滚动数组了,哎嘿。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=1e3 +10 ,mod=998244353 ;int n,k;int f[maxn][maxn],s[maxn][maxn];inline int qpow (int a,int b) { int res=1 ; while (b){ if (b&1 ) res=res*a%mod; b>>=1 ; a=a*a%mod; } return res; }inline int Inv (int x) {return qpow (x,mod-2 );}signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>k; if (n>=k){for (re int i=0 ;i<k;++i) cout<<Inv (k)<<' ' ;return 0 ;} int inv=1 ; for (re int i=1 ;i<=n;++i) inv=inv*i%mod;inv=Inv (inv); f[1 ][0 ]=s[1 ][0 ]=1 ; for (re int j=1 ;j<k;++j) s[1 ][j]=1 ; for (re int i=2 ;i<=n;++i){ for (re int j=0 ;j<k;++j){ int d=((j-i+1 )%k+k)%k; if (d<=j){ if (!d) f[i][j]=s[i-1 ][j]; else f[i][j]=((s[i-1 ][j]-s[i-1 ][d-1 ])%mod+mod)%mod; } else { f[i][j]=s[i-1 ][j]; if (!d) f[i][j]=(f[i][j]+s[i-1 ][k-1 ])%mod; else f[i][j]=(f[i][j]+((s[i-1 ][k-1 ]-s[i-1 ][d-1 ])%mod+mod)%mod)%mod; } if (!j) s[i][j]=f[i][j]; else s[i][j]=(s[i][j-1 ]+f[i][j])%mod; } } for (re int i=0 ;i<k;++i) cout<<f[n][i]*inv%mod<<' ' ; return 0 ; }
好题,可惜只想出来了两个 的无脑做法,感觉正解很牛啊。
先讲讲我的无脑思路:
题目给的条件看起来非常复杂,但事实上并不复杂。
先看 。
可以发现, 代表的含义就是区间 中最靠右侧的最大值的下标。
然后是 。
先看前半部分。可以发现,在固定右端点 时,随着左端点左移, 是单调不增的。
所以 事实上就等于 。
然后又可以发现,在固定左端点时,随着右端点右移, 是单调不减的。
所以 事实上就等于 。
然后看后半部分。
借助上面的结论,可以发现 。
所以 。
最后是 。
把我们得到的 带进去,发现需要求的就是 。
可以发现,只要存在 的取值在 中, 的值就为 。
而我们又知道, 且 随着 的增大单调不减。
所以可以得到结论:若 ,则 ,否则 。
那么现在的问题就是:如何维护 ?
因为我很无脑,所以只想到了无脑做法。
可以发现,修改操作是前缀加。
设前缀加的区间为 ,那么这次操作可能影响的值为 。
因为前缀加不会影响相对大小,所以对 没有影响。
但是对 来说,前缀加以后,可能会有 中的某个数成为它们的前缀最大值。
因为前缀最大值单调不减,所以每次前缀加影响的都是 的一段前缀。
因为有单调性,所以可以二分找出。
那么我们要实现的操作有:
对原序列前缀加,求前缀最大值。
对 序列区间推平,单点查询。
显然可以线段树,于是我们就做到了 。
二分还是有点细节的:因为 的值为最靠右侧的最大值的下标,所以出现多个最大值时要找最右边那个,二分的时候要注意一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include "bits/stdc++.h" #define re register #define int long long #define max(a,b) (a<b?b:a) using namespace std;const int maxn=6e6 +10 ;int n,m;int a[maxn],A[maxn]; struct node { int mx,id; inline node operator + (const node &a)const { if (mx==a.mx) return {mx,max (id,a.id)}; else if (mx>a.mx) return {mx,id}; else return a; } };struct tree1 { int rt,segcnt; int ls[maxn<<1 ],rs[maxn<<1 ]; node s[maxn<<1 ]; int tag[maxn<<1 ]; inline void up (int p) {s[p]=s[ls[p]]+s[rs[p]];} inline void add (int p,int val) {s[p].mx+=val,tag[p]+=val;} inline void down (int p) {if (tag[p]) add (ls[p],tag[p]),add (rs[p],tag[p]),tag[p]=0 ;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){s[p].mx=a[l],s[p].id=l;return ;} int mid=(l+r)>>1 ; build (l,mid,ls[p]),build (mid+1 ,r,rs[p]); up (p); } inline void update (int l,int r,int L,int R,int val,int p) { if (l>=L&&r<=R){add (p,val);return ;} int mid=(l+r)>>1 ;down (p); if (L<=mid) update (l,mid,L,R,val,ls[p]); if (R>mid) update (mid+1 ,r,L,R,val,rs[p]); up (p); } inline node query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return s[p]; int mid=(l+r)>>1 ;down (p); if (R<=mid) return query (l,mid,L,R,ls[p]); if (L>mid) return query (mid+1 ,r,L,R,rs[p]); return query (l,mid,L,R,ls[p])+query (mid+1 ,r,L,R,rs[p]); } }tr1;struct tree2 { int rt,segcnt; int ls[maxn<<1 ],rs[maxn<<1 ]; int ans[maxn<<1 ],tag[maxn<<1 ]; inline void add (int p,int val) {ans[p]=val,tag[p]=val;} inline void down (int p) {if (tag[p]) add (ls[p],tag[p]),add (rs[p],tag[p]),tag[p]=0 ;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){ans[p]=A[l];return ;} int mid=(l+r)>>1 ; build (l,mid,ls[p]),build (mid+1 ,r,rs[p]); } inline void update (int l,int r,int L,int R,int val,int p) { if (L>R) return ; if (l>=L&&r<=R){add (p,val);return ;} int mid=(l+r)>>1 ;down (p); if (L<=mid) update (l,mid,L,R,val,ls[p]); if (R>mid) update (mid+1 ,r,L,R,val,rs[p]); } inline int query (int l,int r,int pos,int p) { if (l==r) return ans[p]; int mid=(l+r)>>1 ;down (p); if (pos<=mid) return query (l,mid,pos,ls[p]); else return query (mid+1 ,r,pos,rs[p]); } }tr2;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (re int i=1 ;i<=n;++i) cin>>a[i]; int mx=0 ,id=0 ; for (re int i=1 ;i<=n;++i){ if (mx<=a[i]) mx=a[i],id=i; A[i]=id; } tr1. build (1 ,n,tr1. rt),tr2. build (1 ,n,tr2. rt); for (re int i=1 ,op,x,y;i<=m;++i){ cin>>op>>x>>y; if (op==1 ){ tr1. update (1 ,n,1 ,x,y,tr1. rt); node res=tr1. query (1 ,n,1 ,x,tr1. rt); int l=x+1 ,r=n,mid,pos=n+1 ; while (l<=r){ mid=(l+r)>>1 ; node tmp=tr1. query (1 ,n,1 ,mid,tr1. rt); if (tmp.mx==res.mx){ if (tmp.id>res.id) pos=mid,r=mid-1 ; else l=mid+1 ; } else if (tmp.mx>res.mx) pos=mid,r=mid-1 ; else l=mid+1 ; } tr2. update (1 ,n,x+1 ,pos-1 ,res.id,tr2. rt); } if (op==2 ){ int pos=tr2. query (1 ,n,y,tr2. rt); if (pos>=x) cout<<0 <<'\n' ; else cout<<x-pos<<'\n' ; } } return 0 ; }
upd:昨天是不是降智了啊?
这凭啥要开两颗线段树,前缀加,查前缀最大值位置,第一颗线段树不是已经维护完了吗?
不过确实启发正解了。
复杂度 ,但因为常数巨大无法通过。
upd:学弟卡过去了,在这放个题解 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include "bits/stdc++.h" #define re register #define int long long #define max(a,b) (a<b?b:a) using namespace std;const int maxn=6e6 +10 ;int n,m;int a[maxn],A[maxn]; struct node { int mx,id; inline node operator + (const node &a)const { if (mx==a.mx) return {mx,max (id,a.id)}; else if (mx>a.mx) return {mx,id}; else return a; } };struct tree1 { int rt,segcnt; int ls[maxn<<1 ],rs[maxn<<1 ]; node s[maxn<<1 ]; int tag[maxn<<1 ]; inline void up (int p) {s[p]=s[ls[p]]+s[rs[p]];} inline void add (int p,int val) {s[p].mx+=val,tag[p]+=val;} inline void down (int p) {if (tag[p]) add (ls[p],tag[p]),add (rs[p],tag[p]),tag[p]=0 ;} inline void build (int l,int r,int &p) { p=++segcnt; if (l==r){s[p].mx=a[l],s[p].id=l;return ;} int mid=(l+r)>>1 ; build (l,mid,ls[p]),build (mid+1 ,r,rs[p]); up (p); } inline void update (int l,int r,int L,int R,int val,int p) { if (l>=L&&r<=R){add (p,val);return ;} int mid=(l+r)>>1 ;down (p); if (L<=mid) update (l,mid,L,R,val,ls[p]); if (R>mid) update (mid+1 ,r,L,R,val,rs[p]); up (p); } inline node query (int l,int r,int L,int R,int p) { if (l>=L&&r<=R) return s[p]; int mid=(l+r)>>1 ;down (p); if (R<=mid) return query (l,mid,L,R,ls[p]); if (L>mid) return query (mid+1 ,r,L,R,rs[p]); return query (l,mid,L,R,ls[p])+query (mid+1 ,r,L,R,rs[p]); } }tr;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (re int i=1 ;i<=n;++i) cin>>a[i]; int mx=0 ,id=0 ; for (re int i=1 ;i<=n;++i){ if (mx<=a[i]) mx=a[i],id=i; A[i]=id; } tr.build (1 ,n,tr.rt); for (re int i=1 ,op,x,y;i<=m;++i){ cin>>op>>x>>y; if (op==1 ) tr.update (1 ,n,1 ,x,y,tr.rt); if (op==2 ){ int pos=tr.query (1 ,n,1 ,y,tr.rt).id; if (pos>=x) cout<<0 <<'\n' ; else cout<<x-pos<<'\n' ; } } return 0 ; }
以下是正解:
在看完题解以后发现我其实也想到这东西了,但是不知道咋做就放弃了。
刚才我们忽略了一个重要性质:每次前缀加的都是一个非负数。
我们将 序列拿出来看,会发现, 中不同元素个数是单调不增的,且 序列是单调不降的。
因为每次前缀加,事实上都是在把后面的一些答案合并到前面。
你可以理解为,一个答案 ,掌管着一段连续的 ,每次前缀加都会把后面一段连续的 合并过来。
而且总合并次数事实上是 的。
我们可以使用并查集+链表实现这个合并。
设 序列去重后得到的序列为 ,即前缀最大值位置的集合。
我们用一个并查集维护 。初始时, 的父亲为 。
然后我们用一个链表维护 。 指向 ,边权为 。
假设现在给 加上 ,那么我们先把 跳到父亲,然后将链表的边权 。
若边权 ,说明需要合并到前面了,那么我们就顺着链表向后跳,然后向前合并,一直跳到无法合并位置。
查询答案时,直接跳到 的父亲,得到的就是 。
复杂度 。
感觉这个非常难说清,大家感受一下吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include "bits/stdc++.h" #define re register #define int long long #define max(a,b) (a<b?b:a) using namespace std;const int maxn=6e6 +10 ;int n,m;int a[maxn],A[maxn],nxt[maxn],c[maxn]; inline int find (int x) {if (x!=A[x]) A[x]=find (A[x]);return A[x];}signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (re int i=1 ;i<=n;++i) cin>>a[i],nxt[i]=n+1 ,c[i]=-1 ; int mx=0 ,id=0 ; for (re int i=1 ;i<=n;++i){ if (mx<=a[i]){ nxt[id]=i,c[id]=a[i]-mx; mx=a[i],id=i; } A[i]=id; } for (re int i=1 ,op,x,y,tmp,del;i<=m;++i){ cin>>op>>x>>y; if (op==1 ){ tmp=find (x); c[tmp]-=y; while (nxt[tmp]<n+1 &&c[tmp]<0 ){ del=nxt[tmp]; c[tmp]+=c[del]; nxt[tmp]=nxt[del]; A[del]=tmp; nxt[del]=n+1 ; c[del]=-1 ; } } else cout<<max (0 ,x-find (y))<<'\n' ; } return 0 ; }
没想到对 KMP 的理解已经烂到这种程度了,看到这种题竟然都想不到。
因为我们要将 拆分成若干 的前缀且数量最少,所以贪心的让每一段长度越长越好。
所以第一个思路就是正着匹配过去,能匹配多长就匹配多长。
但是样例二就把这个思路给否定了:前面一段长的匹配可能导致后面无法匹配。
所以很自然的,想到倒过来匹配。
那么我们要做的就是:每次找到 的一段最长后缀,使得其和 的某个前缀匹配,然后把后缀删掉。
想了半天哈希怎么做,但仔细想一下,最长公共前后缀,这不是 KMP 吗?
所以把 和 拼起来,然后做一遍 KMP。
每次跳 border,直到跳到间隔符为止。
如果某个 border 长度为 ,说明无解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "bits/stdc++.h" #define re register #define int long long using namespace std;const int maxn=2e7 +10 ;int n,m,ans;int nxt[maxn]; string s,s1,s2;signed main () {#ifndef ONLINE_JUDGE freopen ("1.in" ,"r" ,stdin); freopen ("1.out" ,"w" ,stdout);#endif ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m>>s1>>s2; s=' ' +s1+'#' +s2;n=s.length ()-1 ; nxt[1 ]=0 ; for (re int i=2 ,j=0 ;i<=n;++i){ while (j&&s[j+1 ]!=s[i]) j=nxt[j]; if (s[j+1 ]==s[i]) ++j; nxt[i]=j; } int pos=n; while (s[pos]!='#' ){ if (!nxt[pos]){cout<<"Fake" ;return 0 ;} pos=pos-nxt[pos]; ++ans; } cout<<ans; return 0 ; }