-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
106 lines (106 loc) · 44.8 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Nim证明即推导]]></title>
<url>%2F2019%2F07%2F21%2FNim%E8%AF%81%E6%98%8E%E5%8D%B3%E6%8E%A8%E5%AF%BC%2F</url>
<content type="text"><![CDATA[$Nim$游戏:$N$堆石子,每堆$a[i]$个石子,$Alice$与$Bob$轮流行动,$Alice$先行动,操作规则如下1、两方轮流操作2、每次可取走任意一堆的任意数量石子,不能取成负数3、无法行动的一方为输 定义先手必胜局面,N局面,表示当前局面为先手必胜(即面对该局面的玩家一定有获胜的策略)定义先手必败局面,P局面,表示当前局面为先手必败(即面对该局面的玩家一定没有机会获胜)易得到${\color{Red} {1、所有终止局面一定为P局面}}$${\color{Red} {2、可以到达P局面的局面为N面}}$${\color{Red} {3、所有操作都将导致N局面的局面为P局面}}$所以,博弈论的胜负决策可以按一下步骤确定1、标记所有终止状态为P状态2、寻找所有能到达P状态的点,标记为N状态3、寻找所有子状态都为N状态的点,标为P状态。如果没有,则退出但是,显然按上面这种算法会狂T不止,且码量巨大,所以,我们引进一个新函数定义$mex(x)$,$x$为自变量,表示一个集合,$mex(x)$定义为集合中未出现过的最小自然数定义$sg(x)$,令$y$为$x$的子状态集合,$z$为的某一元素$y$,则$sg(x)=mex(sg(z))$又可以得到${\color{Red} {sg(所有终止局面)=0(没有子状态)}}$${\color{Red} {sg(可达终止局面的状态)\ne0}}$${\color{Red} {sg(子状态的sg值均不为0的状态)=0}}$上面三个式子都很显然然后,我们就发现了一个类似的规则当$sg=0$时其状态为P状态当$sg\ne0$时其状态为N状态(对比红字) 如何判断多堆石子时的胜负情况?首先,很显然,当局面为 $\begin{matrix}2n\\\overbrace{ k+k+\cdots+k }\end{matrix}$ 时,先手必输设$N$堆石子,每堆数量为$a_{i}$将每个$a_{i}$转化为二进值,并将前导零补全当$a_{i}=a_{j}=k(1\leq i<j\leq n)$时所有$a_{i}$转化为二进值都是相同的例如,$k=22$时$10110$$10110$每次先手的操作,都可以看做在其中一个$k$中,将一些0变为1,1变为0例如把k取成17$10{\color{Red} {001}}$$10110$此时对手也可以采取相同操作,将下面的也取成17$10{\color{Red} {001}}$$10{\color{Red} {001}}$上面一点大家都懂,只是一个引入 当存在$a_{i}\ne a_{j}$时,如何操作?拿3、5、6为例$011$$101$$110$观察发现,每一纵列都有偶数个1,这意味着当一方取走任意一个1时,另一方都有相同的策略取走另一个1,使其保持纵列仍然为偶数个1当一方将任意一个0改变为1时,另一方都有相同的策略取走一个1(注意,不是补0),使其保持纵列仍然为偶数个1这跟上面讲到过的策略一样,正确性显然所以,我们得出这么一个结论,当每一列的1的个数均为偶数时,先手必败,反之先手必胜再简化一点,当$a_{1}\otimes a_{2}\otimes a_{3}\otimes\cdots\otimes a_{n}=0$时($\otimes$表示异或),先手必败,反之先手必胜再推广,可得一个平等组合游戏中,该游戏的$sg$值等于其所有子游戏的$sg$值的异或和 胜负判断知道了,如何得到必胜策略(如果存在的话)设$a_{1}\otimes a_{2}\otimes a_{3}\otimes\cdots\otimes a_{n}=k(k\ne0)$因为 a_{1}\otimes a_{2}\otimes a_{3}\otimes\cdots\otimes a_{n}=k(k\ne0)很显然 a_{1}\otimes a_{2}\otimes a_{3}\otimes\cdots\otimes a_{n}\otimes k=k\otimes k=0且 k\leq max(a_{1},a_{2},a_{3},\cdots,a_{n})设k的二进值最高位为第x位,则在$a_{1}\sim a_{n}$中,至少存在一个$a_{i}$,二进值第x位也为1所以 a_{i}\otimes k]]></content>
<tags>
<tag>证明</tag>
</tags>
</entry>
<entry>
<title><![CDATA[DFT&IDFT学习笔记]]></title>
<url>%2F2019%2F04%2F14%2FDFT%26IDFT%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[DFT: 多项式定义:\sum_{i=0}^n a_i x^i\\ 系数表示法:f(x)=\sum_{i=0}^n a_i x^i\\ 点值表示法:\{(x_0,f(x_0)),(x_1,f(x_1)),\dots,(x_n,f(x_n))\} 虚数及单位根准备知识 i^2=-1,i=\sqrt{-1}\\ 解方程x^n=1,n个根\\ 欧拉公式e^{\pi i}=-1\\ e^{2\pi i}=1=x^n\\ e^{\frac{2\pi i}{n}}=x\\ \because x_i^n=1\\ \therefore 模长为1,均匀分布在以原点为圆心的一个单位圆上\\ 则设\omega_n^x为方程x^n=1的第x个根(x_0=1,x_1=e^{\frac{2\pi i}{n}},\dots,x_k=e^{\frac{2\pi ik}{n}},\dots,x_{n-1}=e^{\frac{2\pi i(n-1)}{n}})\\ 称\omega_n^1(即\omega_n)为单位根\\ \omega_n^k=e^{\frac{2\pi ik}{n}}\\ 单位根引理1: \forall\omega_{nk}^{tk},\omega_{nk}^{tk}=e^{\frac{2\pi itk}{nk}}=e^{\frac{2\pi it}{n}}=\omega_{n}^{t}\\ 即上下同时乘一个数依然成立\\单位根引理2: \omega_n^{\frac n 2+k}=e^{\frac{2\pi i(\frac n 2+k)}{n}}=e^{\frac{\pi in+2\pi ik}{n}}\\ =e^{\frac{\pi in}{n}}\times e^{\frac{2\pi ik}{n}}=e^{\pi i}\times e^{\frac{2\pi ik}{n}}\\ =e^{\pi i}\times e^{\frac{2\pi ik}{n}}=-e^{\frac{2\pi ik}{n}}=-\omega_n^k\\ 即\omega_n^{\frac n 2+k}=-\omega_n^k 由玄学内容可知\\ 将\omega_n^k代入f(x)并将其转为点值表示法很快\\ 则f(\omega_n^k)=\sum_{i=0}^n a_i\omega_n^{ki}\\ 在这令n为2^m(m为使n\leq2^m的最小值),之后的a_i补零便于二分\\ f(\omega_n^k)=\sum_{i=0}^{\frac n 2-1} a_{2i}\omega_n^{2ki}+\sum_{i=0}^{\frac n 2-1} a_{2i+1}\omega_n^{k(2i+1)}\\ f(\omega_n^k)=\sum_{i=0}^{\frac n 2-1} a_{2i}\omega_n^{2ki}+\omega_n^k\sum_{i=0}^{\frac n 2-1} a_{2i+1}\omega_n^{2ki}\\ f(\omega_n^k)=\sum_{i=0}^{\frac n 2-1} a_{2i}\omega_{\frac n 2}^{ki}+\omega_n^k\sum_{i=0}^{\frac n 2-1} a_{2i+1}\omega_{\frac n 2}^{ki}\\ 令0\leq k]]></content>
<tags>
<tag>证明</tag>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈KMP字符串匹配算法]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88KMP%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D%E7%AE%97%E6%B3%95%2F</url>
<content type="text"><![CDATA[Ⅰ、预备知识$KMP$,全称$Knuth-Morris-Pratt$算法,可以实现高效搜索一个模式串P(长为m)在文本串S(长为n)中出现的位置,其核心是处理$next$数组,预处理模式串失配后将匹配的位置,从而实现高效搜索普通的模式串搜索,是暴力一位一位枚举$S[i]$与$P[j]$是否相等,不相等则往后移一位,重新匹配代码大概长这样:1234567891011inline void check(char *p,char *s){ int i=1,j=1,lst=1; while(1){ if(j==m+1) return lst; if(s[i]==p[j]) i++,j++; else j=1,i=++lst; }} 很明显,这种写法做了许多无用功,一些显然无法匹配的段也要$O(m)$去搜索。最坏情况下,这种写法可以被卡成$O(n\times m)$于是,$kmp$算法诞生了$kmp$的核心思想为不一位一位暴力匹配,而是通过Next数组大大减少匹配数量Next数组表示什么呢?设$s_{i\ldots j}$表示字符串$s$的第$i$位到第$j$位,i表示文本串已经处理了多少点,j表示模式串已经匹配了多少点Next数组表示当$s1[i+1]!=s2[j+1]$时,即模式串与文本串下一位失配时,j指针要去往的位置(s1表示文本串,s2表示模式串)如图,当i=4,j=4时,如果模式串的下一位与文本串的下一位不匹配,则j就转移到Next[j],即1,使得j再次与i匹配显然,处理nxt数组可以通过求最长严格前缀与后缀公共串的长度得到以下代码:12345678910inline void get_Next(){ int j=0; F(i,1,l2){//l2为模式串的长度 while(j&&s2[j+1]!=s2[i+1])//s2为模式串,如果没有跳到头就继续匹配 j=Next[j];//失配就往前跳 if(s2[j+1]==s2[i+1])//如果下一位相同 j++;//模式串+1 Next[i+1]=j;//处理Next数组 }} 接下来就是模式串匹配,思路和求Next数组差不多,匹配则往后一位,失配则跳Next数组,代码如下:12345678910111213inline void kmp(){ int j=0; F(i,0,l1-1){ while(j&&s2[j+1]!=s1[i+1])//失配 j=Next[j]; if(s2[j+1]==s1[i+1]) j++; if(j==l2){//匹配成功 printf("%d\n",i-j+2);//此处为(i+1)-j+1 j=Next[j];//继续找所有的匹配串 } }}]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[字符串算法模板合集]]></title>
<url>%2F2019%2F03%2F23%2F%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%AE%97%E6%B3%95%E6%A8%A1%E6%9D%BF%E5%90%88%E9%9B%86%2F</url>
<content type="text"><![CDATA[1、KMPKMP模板123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include<iostream>#include<cstdio>#include<cmath>#include<algorithm>#include<cstring>#define ll long long#define INF 2147483647#define mem(i,j) memset(i,j,sizeof(i))#define F(i,j,n) for(register int i=j;i<=n;i++)using namespace std;char s1[1000010],s2[1000010];int Next[1000010],l1,l2;inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} if(okoko==1)return -datta;return datta;}inline void get_Next(){ int j=0; F(i,1,l2){ while(j&&s2[j+1]!=s2[i+1]) j=Next[j]; if(s2[j+1]==s2[i+1]) j++; Next[i+1]=j; }}inline void kmp(){ int j=0; F(i,1,l1){ while(j&&s2[j+1]!=s1[i]) j=Next[j]; if(s2[j+1]==s1[i]) j++; if(j==l2){ printf("%d\n",i-j+1); j=Next[j]; } }}int main(){ scanf("%s%s",s1+1,s2+1); l1=strlen(s1+1);l2=strlen(s2+1); get_Next(); kmp(); F(i,1,l2-1) printf("%d ",Next[i]); printf("%d\n",Next[l2]); return 0;}]]></content>
<tags>
<tag>模板</tag>
</tags>
</entry>
<entry>
<title><![CDATA[USACO13NOV 没有找零No Change 题解]]></title>
<url>%2F2019%2F03%2F23%2FUSACO13NOV%20%E6%B2%A1%E6%9C%89%E6%89%BE%E9%9B%B6No%20Change%20%E9%A2%98%E8%A7%A3%2F</url>
<content type="text"><![CDATA[题目描述约翰到商场购物,他的钱包里有K(1 <= K <= 16)个硬币,面值的范围是1..100,000,000。 约翰想按顺序买 N个物品(1 <= N <= 100,000),第i个物品需要花费c(i)块钱,(1 <= c(i) <= 10,000)。 在依次进行的购买N个物品的过程中,约翰可以随时停下来付款,每次付款只用一个硬币,支付购买的内容是从上一次支付后开始到现在的这些所有物品(前提是该硬币足以支付这些物品的费用)。不幸的是,商场的收银机坏了,如果约翰支付的硬币面值大于所需的费用,他不会得到任何找零。请计算出在购买完N个物品后,约翰最多剩下多少钱。如果无法完成购买,输出-1 输入输出格式输入格式: Line 1: Two integers, K and N. Lines 2..1+K: Each line contains the amount of money of one of FJ’s coins. Lines 2+K..1+N+K: These N lines contain the costs of FJ’s intended purchases. 输出格式: Line 1: The maximum amount of money FJ can end up with, or -1 if FJ cannot complete all of his purchases. 输入输出样例输入样例#1:3 6121510633237 输出样例#1: 复制12 说明FJ has 3 coins of values 12, 15, and 10. He must make purchases in sequence of value 6, 3, 3, 2, 3, and 7. FJ spends his 10-unit coin on the first two purchases, then the 15-unit coin on the remaining purchases. This leaves him with the 12-unit coin. 题目大意: 给出k个硬币,n个商品,可以调整使用硬币的顺序,依次购买商品,每次不会找零,求最后剩下的最大钱数,如不能买全,输出-1 因为$k\leq16$,考虑状压 设$dp_{sta}$表示状态为$sta$时(二进制,表示哪些硬币使用了,哪些没使用),从一号物品开始能购买的物品数量 显然有 当使用当前状态中未使用的一个硬币时 $dp[new_sta]=max(dp[new_sta],k)$ 其中$new_sta$是转移后的新状态 用$sum_k$表示前$i$个物品的总售价 则k为$sum_k$能够支付起是的最大物品数量(最优性) 二分一下即可求出k 代码:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include<bits/stdc++.h>#define ll long long#define INF 2147483647#define mem(i,j) memset(i,j,sizeof(i))#define F(i,j,n) for(register int i=j;i<=n;i++)#define lowbit(i) i&(-i)using namespace std;int k,n,c[20],p[100010],dp[1100000],sum[100010];inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}int main(){ k=read();n=read(); F(i,1,k) c[i]=read(),c[0]+=c[i]; F(i,1,n) p[i]=read(),sum[i]=sum[i-1]+p[i]; mem(dp,-1); dp[0]=0; int ans=2147483647; F(sta,0,(1<<k)-1){ if(dp[sta]==-1) continue; if(dp[sta]==n){ int _ans=0; F(i,1,k) if(sta&(1<<(i-1))) _ans+=c[i]; ans=min(ans,_ans); continue; } F(i,1,k){ if(sta&(1<<(i-1))) continue; int l=dp[sta]+1,r=n,mid,_ans=-1; while(l<=r){ mid=(l+r)>>1; if(sum[mid]<=sum[dp[sta]]+c[i]) l=mid+1,_ans=mid; else r=mid-1; } if(_ans==-1) continue; dp[sta|(1<<(i-1))]=max(dp[sta|(1<<(i-1))],_ans); } } printf("%d\n",ans==2147483647?-1:c[0]-ans); return 0;}]]></content>
<tags>
<tag>题解</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈斯坦纳树 && [WC2008]游览计划题解]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88%E6%96%AF%E5%9D%A6%E7%BA%B3%E6%A0%91%20%26%26%20%5BWC2008%5D%E6%B8%B8%E8%A7%88%E8%AE%A1%E5%88%92%E9%A2%98%E8%A7%A3%2F</url>
<content type="text"><![CDATA[Ⅰ、抛出问题题目描述从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。主办者将绍兴划分为$N$行$M$列$(N×M)$个分块,如下图$(8×8)$:景点含于方块内,且一个方块至多有一个景点。无景点的方块视为路。为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者;在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。例如,在上面的例子中,在每个没有景点的方块中填入一个数字,表示控制该方块最少需要的志愿者数目:图中用深色标出的方块区域就是一种可行的志愿者安排方案,一共需要20名志愿者。由图可见,两个相邻的景点是直接(有景点内的路)连通的(如沈园和八字桥)。现在,希望你能够帮助主办方找到一种最好的安排方案。 Input第一行有两个整数,N和M,描述方块的数目。接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,行首行末也可能有多余的空格。 Output第一行有两个整数,N和M,描述方块的数目。接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,行首行末也可能有多余的空格。 Sample Input123454 40 1 1 02 5 5 11 5 5 10 1 1 0 Sample Output1234xoox___o___oxoox HINT所有的 10 组数据中 N, M ,以及景点数 K 的范围规定如下输入文件中的所有整数均不小于 0 且不超过 2^16感谢@panda_2134 提供Special Judge Ⅱ、分析问题斯坦纳树,用于解决一类生成树问题,如在图上做最小生成树,使得每个目标点互相联通的这种题对于此题,设$dp[i][sta]$表示在$i$号节点,与目标点的连通性为$sta$的最小花费则有 dp[i][sta]=min_{s\in sta} \{dp[i][s]+dp[i][sta\otimes s]-val[i]\}大意为$s$为$sta$的子集,$dp[i][sta]$的值则等于其两个互补的子集之和,因为$i$号节点算了两次,所以最后要减去一个$val[i]$,表示减去加入$i$号点所需的花费同时,还有状态转移方程 dp[i][sta]=\min\{dp[j][sta]+val[i](i\to j)\}表示$j$与目标点的连通性为$sta$,即再连一条$i\to j$的边即可让$i$与目标点的连通性变为$sta$第二个状态转移方程用SPFA暴力更新即可123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687#include<bits/stdc++.h>#define F(i,j,n) for(register int i=j;i<=n;i++)#define INF 0x3f3f3f3f#define ll long long#define mem(i,j) memset(i,j,sizeof(i))#define pii pair<int,int>using namespace std;struct hahaha{ int x,y,sta;}pre[20][20][1050];const int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};//位移数组int n,m,mp[50][50],cnt=0,f[50][50][2050],lx,ly;//本题中点的标号按坐标存,即(i,j)表示第一维queue<pii>q;//SPFA队列bool vis[50][50];//(i,j)这个点是否走过inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}inline void SPFA(int now_sta){ while(!q.empty()){ int x=q.front().first,y=q.front().second; vis[x][y]=0; F(p,0,3){ int tx=x+dx[p],ty=y+dy[p]; if(tx>=1&&tx<=n&&ty>=1&&ty<=m){ if(f[tx][ty][now_sta]>f[x][y][now_sta]+mp[tx][ty]){ f[tx][ty][now_sta]=f[x][y][now_sta]+mp[tx][ty];//转移 pre[tx][ty][now_sta].x=x; pre[tx][ty][now_sta].y=y; pre[tx][ty][now_sta].sta=now_sta;//记录一下有哪个状态转移而来,方便处理第二问 if(!vis[tx][ty]) vis[tx][ty]=1,q.push(make_pair(tx,ty)); } } } q.pop(); }}inline void dfs(int x,int y,int sta){ if(!pre[x][y][sta].x&&!pre[x][y][sta].y)//如果没有,说明走到了一条分支的尽头,return return ; vis[x][y]=1;//标记走过 if(pre[x][y][sta].x==x&&pre[x][y][sta].y==y)//如果是由(x,y)位置的另一个状态转移而来(即主函数里的转移) dfs(x,y,sta^pre[x][y][sta].sta);//递归寻找补集 dfs(pre[x][y][sta].x,pre[x][y][sta].y,pre[x][y][sta].sta);//寻找周围节点(即SPFA中的转移)}int main(){ mem(f,0x3f); n=read();m=read(); F(i,1,n) F(j,1,m){ mp[i][j]=read(); if(!mp[i][j]) f[i][j][1<<(cnt++)]=0,lx=i,ly=j;//随便记录一个标记点记做(lx,ly) } F(sta,0,(1<<cnt)-1){//枚举联通状态 F(i,1,n){ F(j,1,m){//枚举坐标 for(int s=sta;s;s=(s-1)&sta){//枚举子集 if(f[i][j][sta]>f[i][j][s]+f[i][j][s^sta]-mp[i][j]){ f[i][j][sta]=f[i][j][s]+f[i][j][s^sta]-mp[i][j];//转移 pre[i][j][sta].x=i; pre[i][j][sta].y=j; pre[i][j][sta].sta=s;//记录由哪个状态转移而来 } } if(f[i][j][sta]<0x3f3f3f3f) q.push(make_pair(i,j)),vis[i][j]=1;//如果有值,加入SPFA更新队列 } } SPFA(sta);//SPFA暴力更新周围节点 } mem(vis,0); printf("%d\n",f[lx][ly][(1<<cnt)-1]);//第一问 dfs(lx,ly,(1<<cnt)-1);//寻找走了哪些点 F(i,1,n) F(j,1,m){ if(!mp[i][j])//如果是障碍物 printf("x"); else printf("%c",vis[i][j]==1?'o':'_');//走过就o,没走过就_ printf("\n"); } return 0;}]]></content>
<tags>
<tag>算法</tag>
<tag>题解</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【置顶】博客说明]]></title>
<url>%2F2019%2F03%2F23%2F%5B%E7%BD%AE%E9%A1%B6%5D%E5%8D%9A%E5%AE%A2%E8%AF%B4%E6%98%8E%2F</url>
<content type="text"><![CDATA[AFO]]></content>
</entry>
<entry>
<title><![CDATA[浅谈manacher回文串算法]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88manacher%E5%9B%9E%E6%96%87%E4%B8%B2%E7%AE%97%E6%B3%95%2F</url>
<content type="text"><![CDATA[Ⅱ、预备知识$manacher$算法,俗称马拉车,主要用于解决字符串中的回文子串问题 Ⅲ、抛出问题模板题 题目描述给出一个只由小写英文字符$a,b,c…y,z$组成的字符串$S$,求$S$中最长回文串的长度.字符串长度为$n$ 输入输出格式输入格式:一行小写英文字符$a,b,c…y,z$组成的字符串$S$ 输出格式:一个整数表示答案 输入输出样例输入样例#1:aaa 输出样例#1:3 说明:字符串长度$len\leq11000000$ Ⅳ、分析问题]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈斜率优化dp]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88%E6%96%9C%E7%8E%87%E4%BC%98%E5%8C%96dp%2F</url>
<content type="text"><![CDATA[Ⅰ、前置知识$y=kx+b$$k$叫斜率,$b$叫截距$(x_1,y_1)$$(x_2,y_2)$两点连成的直线的斜率$k=\frac{y1-y2}{x1-x2}$ Ⅱ、抛出问题洛谷板子 题目描述$n$个任务排成一个序列在一台机器上等待完成(顺序不得改变),这$n$个任务被分成若干批,每批包含相邻的若干任务。从时刻$0$开始,这些任务被分批加工,第$i$个任务单独完成所需的时间是$T_i$。在每批任务开始前,机器需要启动时间$S$,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数$F_i$。请确定一个分组方案,使得总费用最小。例如:$S=1$;$T=\{1,3,4,2,1\}$;$F=\{3,2,3,3,4\}$。如果分组方案是$\{1,2\}$、$\{3\}$、$\{4,5\}$,则完成时间分别为$\{5,5,10,14,14\}$,费用$C=\{15,10,30,42,56\}$,总费用就是$153$。 输入输出格式输入格式:第一行是$n(1\leq n\leq5000)$。第二行是$S(0\leq S\leq50)$。下面$n$行每行有一对数,分别为$T_i$和$F_i$,均为不大于$100$的正整数,表示第$i$个任务单独完成所需的时间是$T_i$及其费用系数$F_i$。 输出格式:一个数,最小的总费用。 输入输出样例输入样例#1:511 33 24 32 31 4 输出样例#1:153 Ⅲ、分析问题首先这题$O(n^2)$可以艹过但是$O(n^2)$过了这题讲斜率优化毫无意义QAQ所以请自动将数据范围改成$(1\leq n\leq500000)$ 先来看一眼普通$dp$$O(n^2)$怎么写设$f[i]$表示处理到第$i$个任务,前$i$个的最小费用$t_i$表示时间的前缀和$c_i$表示费用的前缀和考虑从$j$转移到$i$,表示$j+1$到$i$打包到一批则状态转移方程为 f[i]=\min_{j=1}^{i}\{f[j]+s\times(c_n-c_j)+t_i\times(c_i-c_j)\}由于之前哪些任务被分成一批不好处理,所以可以直接加上$s\times(c_n-c_j)$当作对后续状态的处理然后推式子将$j$看作一个变量,然后去掉$\min$,得到 f[i]=f[j]+s\times(c_n-c_j)+t_i\times(c_i-c_j)拆括号 f[i]=f[j]+s\times c_n-s\times c_j+t_i\times c_i-t_i\times c_j移项 f[i]-s\times c_n+s\times c_j-t_i\times c_i+t_i\times c_j=f[j]f[j]=f[i]-s\times c_n+s\times c_j-t_i\times c_i+t_i\times c_j提取公因式 f[j]=c_j\times(s+t_i)+f[i]-s\times c_n+-t_i\times c_i此时式子推成这样再看一眼前置知识$y=kx+b?$此时我们的式子就像一条直线解析式! x=c_j,y=f[j],k=s+t_i,b=f[i]-s\times c_n+-t_i\times c_i我们想要最小化$f[i]$,就是最小化$b$而我们此时要做的,就是用一条已知斜率的直线,利用已有的坐标,找到一个最小的$b$如图,现在处理到$i$,则共有$i-1$个坐标为$(z_j,f[j])(1\leq j < i )$的点如图所示右下角为当前处理的斜率为$s+t_i$的直线,我们要将它向上平移,直到和上方$i-1$个点中的一个相交显然要找的点$j$(也叫决策点)一定在图形的凸包上又很显然,决策点一定在下凸壳上,因为下凸壳上的点显然比上凸壳更优又很显然,决策点一定在下凸壳的右半侧,因为$k$(即$s+t_i$)一定大于$0$叕很显然找到决策点之后是这样的如何找到决策点?观察可以知道,凸包上的直线的斜率具有单调性二分!每次二分一个点,$check$这个点左侧的直线的斜率是否小于$s+t_i$,右侧的斜率是否大于$s+t_i$,如果是就证明找到了决策点手玩一下更好理解找到决策点之后,很明显,决策点左边所有的直线的截距都要大于$s+t_i$,所以左边的所有点都没有当前点优于是拿单调队列存一下凸包是上的点,如果没有当前直线优则踢掉找到决策点后,相当于找到了$j$,更新$f[i]$更新完$f[i]$后,为了方便后续的查找,将$(c_i,f[i])$插入凸包,并且维护一下凸包的单调性最后输出$f[n]$即可代码:12345678910111213141516171819202122232425262728293031323334#include<bits/stdc++.h>#define F(i,j,n) for(register int i=j;i<=n;i++)#define INF 0x3f3f3f3f#define ll long long#define mem(i,j) memset(i,j,sizeof(i))using namespace std;int n,s,c[5010],t[5010],f[5010],q[5010],l,r;inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}int main(){ n=read();s=read(); F(i,1,n){ t[i]=t[i-1]+read(); c[i]=c[i-1]+read(); } mem(f,0x3f); f[0]=0; l=1;r=0; q[++r]=0; F(i,1,n){ while(l<r&&f[q[l+1]]-f[q[l]]<=(s+t[i])*(c[q[l+1]]-c[q[l]]))//避免精度误差 l++;//由于博主太菜了所以用的是线性而不是二分 f[i]=f[q[l]]+s*(c[n]-c[q[l]])+t[i]*(c[i]-c[q[l]]);//更新 while(l<r&&(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])<=(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])) r--; q[++r]=i; } printf("%d\n",f[n]); return 0;}]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈计数排序]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%2F</url>
<content type="text"><![CDATA[计数排序是一种非基于比较的排序算法,本文主要介绍计数排序的思想及代码实现 Ⅱ、分析问题对于数组$a_1\sim a_n$,将其从小至大排序步骤:1、处理出数据中每个数出现的频率,放入数组$b$中2、处理处数据频率表的前缀形式,此时第$i$个位置的数据表示$a$数组的全部数据中,小于等于$a_i$的数量(包括a_i本身)3、依照前缀表处理出排好序的数组例如,现在有数组$a=\begin{Bmatrix}7,3,2,2,6,3,7,9,8,9\end{Bmatrix}$,用计数排序将其排序处理出$a$数组的频次表处理出b数组的前缀和此时处理出前缀和之后,可以发现,$i$对应的$a_i$,恰好为$i$对应在排好序中的序列的最后一位的编号(或者没有)于是代码就可以这么写:12345678910111213141516171819202122232425#include<bits/stdc++.h>#define F(i,j,n) for(register int i=j;i<=n;i++)#define INF 0x3f3f3f3f#define ll long long#define mem(i,j) memset(i,j,sizeof(i))using namespace std;int n,m,a[100010],b[100010],c[100010];inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}int main(){ n=read();m=read(); F(i,1,n) a[i]=read(),b[a[i]]++;//b数组用来处理频率 F(i,1,m) b[i]+=b[i-1];//处理前缀和 F(i,1,n) c[b[a[i]]--]=a[i];//将a[i]放入对应c数组的位置中,并将频率减一,可以手动模拟下 F(i,1,n) printf("%d ",c[i]); return 0;}]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[莫比乌斯反演推导即μ函数的证明]]></title>
<url>%2F2019%2F03%2F23%2F%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%8F%8D%E6%BC%94%E6%8E%A8%E5%AF%BC%E5%8D%B3%CE%BC%E5%87%BD%E6%95%B0%E7%9A%84%E8%AF%81%E6%98%8E%2F</url>
<content type="text"><![CDATA[题目描述求长度为$n$且仅包含小写英文字母且循环节长度恰为$n$的字符串的个数。意思是求一个长度为$n$的字符串的个数,它的任意一个子串重复若干次都不能和原串相等设$f(n)$为长度为$n$的字符串个数,设$g(n)$为题目所求答案的个数显然 f(n)=26^n,f(n)=\sum_{d\mid n}g(d)第一个很显然第二个意思是 任意一个长度为$d(d\mid n)$的串,重复$\frac{n}{d}$次的和,就是长度为$n$的字符串的个数又设 \sum_{d\mid n}\mu(d)=[n=1]$[n=1]$的意思为当$n=1$时,表达式值为$1$,否则为$0$因为 g(n)=\sum_{m\mid n}[\frac{n}{m}=1]g(m)当表达式为真时,$n=m$,$g(n)=g(m)$将$\sum_{d\mid n}\mu(d)=[n=1]$代入 g(n)=\sum_{m\mid n}\sum_{d\mid\frac{n}{m}}\mu(d)g(m)因为$m\mid n$,$d\mid\frac{n}{m}$,所以可以先枚举d g(n)=\sum_{d\mid n}\mu(d)\sum_{m\mid\frac{n}{d}}g(m)再看一开始的$f(n)$ f(n)=\sum_{d\mid n}g(d)g(n)=\sum_{d\mid n}\mu(d)\sum_{m\mid\frac{n}{d}}g(m)代入 g(n)=\sum_{d\mid n}\mu(d)f(\frac{n}{d})调换下标$d\to \frac{n}{d}$因为枚举$d\mid n$等价于$\frac{n}{d}\mid n$ g(n)=\sum_{d\mid n}\mu(\frac{n}{d})f(d)因为 f(n)=26^n(O(logn)快速幂)\sum_{d\mid n}枚举因数(O(\sqrt n))如何求$\mu(n)$? 已知x=\prod_{i=1}^{K}p_i^{a_i}(p_i\in prime),\mu(x)=\prod_{i=1}^{K}[a_i=1](−1)求证\sum_{d\mid n}\mu(d)=[n=1]证明:设$f(n)=\sum_{d\mid n}\mu(d)$显然$f(1)=\mu(1)=1$假设已经证明$f(1)$到$f(n-1)$,$f(1)=1$,$f(2)$到$f(n-1)$都为0当存在某一个$a_i>0$时 f(n)=\sum_{d\mid n}\mu(d),f(\frac{n}{p_i})=\sum_{d\mid\frac{n}{p_i}}\mu(d)\because\frac{n}{p_i}a_i-1(d\not\mid\frac{n}{p_i})\end{cases}\therefore b_i=a_i\because a_i>1\therefore b_i>1\mu(d)=0\sum_{d\not\mid\frac{n}{p_i},d\mid n}\mu(d)=0\therefore f(n)=0+0=0当对于所有$a_i$,$a_i=1$时 f(n)=\sum_{d\mid n}\mu(d)f(n)=\sum_{d\mid\frac{n}{p_i}}\mu(d)+\sum_{d\mid\frac{n}{p_i}}\mu(d\times p_i)\because a_i=1所以$d$中不含$p_i$因式 \therefore\sum_{d\mid\frac{n}{p_i}}\mu(d\times p_i)=-\sum_{d\mid\frac{n}{p_i}}\mu(d)\therefore f(n)=\sum_{d\mid\frac{n}{p_i}}\mu(d)-\sum_{d\mid\frac{n}{p_i}}\mu(d)=0综上所述,$f(n)=[n=1]$]]></content>
<tags>
<tag>证明</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈CDQ分治]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88CDQ%E5%88%86%E6%B2%BB%2F</url>
<content type="text"><![CDATA[Ⅰ、预备知识整体二分??? Ⅱ、抛出问题我们先来看一道洛谷的模板题 题目背景这是一道模板题可以使用bitset(不会),CDQ分治,K-DTree(不会)等方式解决。 题目描述有$n$个元素,第$i$个元素有$a_i$、$b_i$、$c_i$三个属性,设$f(i)$表示满足$a_j\leq a_i$且$b_j\leq b_i$且$c_j\leq c_i$的$j$的数量。对于$d\in[0, n)$,求$f(i)=d$的数量 输入输出格式输入格式:第一行两个整数$n、k$,分别表示元素数量和最大属性值。之后$n$行,每行三个整数$a_i$、$b_i$、$c_i$,分别表示三个属性值。 输出格式:输出$n$行,第$d+1$行表示$f(i)=d$的$i$的数量。 输入输出样例输入样例#1:10 33 3 32 3 32 3 13 1 13 1 21 3 11 1 21 2 21 3 21 2 1 输出样例#1:3130101001 说明:$1\leq N\leq100000,1\leq k\leq200000$ Ⅲ、分析问题CDQ分治,有国家队某巨佬发明(仿佛是插头dp的论文作者???),主要用于解决带修改,查询,可排序序列的一系列问题,仅可支持离线操作CDQ分治的主要步骤有以下几点:1、读入(废话)1、将已经读入好的数据按照某关键字排序2、设当前区间为$[l,r]$,递归处理左区间$[l,mid]$和右区间$[mid+1,r]$,计算左区间的修改操作对右区间的影响(一般用树状数组等数据结构维护)3、清除数据结构内的修改数据本题又叫三维偏序问题,是CDQ分治的经典题型先按照第一维(即$a_i$)排序,这样就将问题转化到了二维设当前区间为$[l,r]$讲$[l,mid]$和$[mid+1,r]$分别按照第二维排序,此时在左区间中的$a$均小于有区间中的$a$(保证第一维),设左区间已访问到$pl$,右区间已访问到$pr$$(l\leq pl\leq mid,mid+1\leq pr\leq r)$当$b[pl]<=b[pr]$时(保证第二维),即将$pl$点的$c$值加入树状数组统计比$pr$点的$c$值小或等于的点的数量(保证第三维)详见代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include<bits/stdc++.h>#define ll long long#define INF 2147483647#define mem(i,j) memset(i,j,sizeof(i))#define F(i,j,n) for(register int i=j;i<=n;i++)#define lowbit(i) i&(-i)//树状数组using namespace std;struct hahaha{ int x,y,z,ans,cnt;//x,y,z分别对应a,b,c;ans表示题目中的f(i),即三维都小于等于i的数量,cnt表示x,y,z相等的点的数量,若只出现一次,则cnt=1}f[100010],s[100010];//f为输入数据,s为处理后数据int nn,n,m,ans[100010],c[200010];//c是树状数组上的点,ans为最终答案inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}inline bool cmpx(hahaha a,hahaha b){//以x为第一关键字排序 return a.x==b.x?a.y==b.y?a.z<b.z:a.y<b.y:a.x<b.x;}inline bool cmpy(hahaha a,hahaha b){//以y为第一关键字排序 return a.y==b.y?a.z<b.z:a.y<b.y;}inline void add(int x,int v){//树状数组修改 for(int i=x;i<=m;i+=lowbit(i)) c[i]+=v;}inline int ask(int x){//树状数组查询 int res=0; for(int i=x;i;i-=lowbit(i)) res+=c[i]; return res;}class CDQ_DC{//之所以用class写是为了装逼 private: public: inline void CDQ(int l,int r){ if(l==r)//边界条件 return ; int mid=(l+r)>>1; CDQ(l,mid); CDQ(mid+1,r);//递归处理左右区间 sort(s+l,s+mid+1,cmpy); sort(s+mid+1,s+r+1,cmpy);//按y排序 int pl=l,pr=mid+1; while(pr<=r){ while(pl<=mid&&s[pl].y<=s[pr].y) add(s[pl].z,s[pl].cnt),pl++;//加点 s[pr].ans+=ask(s[pr].z);//处理pr的ans pr++; } F(i,l,pl-1) add(s[i].z,-s[i].cnt);//清空树状数组 }}C;int main(){ nn=read();m=read(); F(i,1,nn) f[i].x=read(),f[i].y=read(),f[i].z=read(); sort(f+1,f+nn+1,cmpx);//按x排序 int ct=0; F(i,1,nn){ ct++; if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y||f[i].z!=f[i+1].z){ s[++n]=f[i]; s[n].cnt=ct;//处理数据 ct=0; } } C.CDQ(1,n); F(i,1,n) ans[s[i].ans+s[i].cnt-1]+=s[i].cnt;//处理最后答案 F(i,0,nn-1) printf("%d\n",ans[i]); return 0;}]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈kdtree]]></title>
<url>%2F2019%2F03%2F23%2F%E6%B5%85%E8%B0%88kdtree%2F</url>
<content type="text"><![CDATA[Ⅰ、抛出问题Description有一列元素,每一个元素有三个属性:标号、标识符、数值。这些元素按照标号从1~n排列,标识符也是1~n的一个排列,初始时数值为0。当然我们可以把每个元素看成一个多维数字,那么这列元素就是一个数列。现在请你维护这个数列,使其能支持以下两种操作:1.将标号为l~r的所有元素的数值先乘上x,再加上y;2.将标识符为l~r的所有元素的数值先乘上x,再加上y。当然你还得回答某些询问:1.标号为l~r的所有元素的数值的和;2.标识符为l~r的所有元素的数值的和。 Input第一行有两个正整数n、m,分别表示数列长度和操作与询问个数的总和。第二行有n个正整数,表示每个元素的标识符,保证这n个数是1~n的一个排列。接下来m行,每行的第一个数字为op。若op为0,则表示要进行第一个操作,接下去四个数字表示l,r,x,y;若op为1,则表示要进行第二个操作,接下去四个数字表示l,r,x,y;若op为2,则表示要回答第一个询问,接下去两个数字表示l,r;若op为3,则表示要回答第二个询问,接下去两个数字表示l,r。 Output包含若干行,每行表示一个询问的答案。由于答案可能很大,只要请你输出答案对536870912取模后的值即可。 Sample Input4 42 1 4 30 2 3 4 51 1 3 4 72 1 13 1 1 Sample Output727HINT第一次操作后,数列变为0 5 5 0第二次操作后,数列变为7 27 5 7N,M<=50000, 1<=L<=R<=N 0<=X,Y<=2^31-1 Sourcebzoj4303数列 Ⅱ、分析问题kdtree,全称k-dimensional-tree,意思即为k维树,主要用于解决高维空间的修改查询操作,支持打标记,求最近最远点对等,类似于线段树等数据结构,接下来就来详细讲讲kdtree的写法 1、维护的数据写数据结构,一定要弄清维护了哪些数据kdtree是一种类似于线段树一样的数据结构,树上每一个节点管辖k维区间中的某一个范围,存每个维度的最大最小值以确定边界代码:注:代码中给的是二维kdtree的模板,所以只有两位1234567struct hahaha{ int tp,ls,rs,v[2],Max[2],Min[2],val;//tp为当前节点维护的是哪一维,ls,rs分别为左右儿子编号,v存节点坐标,Max和Min维护当前节点管辖区间的最大最小(即边界),val存当前点的权值 int cnt,mlt,sum,len;//cnt加标记,mlt乘标记,sum区间和,len区间长度 bool operator<(const hahaha &y)const{ return v[T]<y.v[T];//排序方便寻找中位数 }}tree[50010]; 2、建树如图,可以看出,kdtree是以一位一位顺次分割的方式建树的,每次寻找区间中的中位数点,沿当前维度进行分割,如本图为先竖着再横着分割第一次先找到横向的中位数,竖着分割一次(已用红色标出),在递归左右子树,找竖着的中位数,横向分割,再往下依次递归以下建树部分代码:12345678910111213141516171819202122inline void updata(int p){//很显然的更新 tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0]));//x坐标的最小值 tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1]));//y坐标的最小值 tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0]));//x坐标的最大值 tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1]));//y坐标的最大值}inline int build_tree(int l,int r,int tp){//l,r为区间,tp为区间维度 T=tp;//T也是区间维度,用于查找中位数 int mid=((l+r)>>1),p=mid; nth_element(tree+l,tree+mid,tree+r+1);//这是个查找中位数的神奇函数 tree[p].tp=tp; tree[p].mlt=1; tree[p].len=r-l+1;//更新节点信息 if(l<mid) ls(p)=build_tree(l,mid-1,tp^1);//其实这个地方严谨来说应该是(tp+1)%2,因为维度是顺次遍历,假如说有5维,那就是按照0,1,2,3,4,0,1,2...这样的顺序遍历。 //注意,这里不是像线段树一样l,mid,而是l,mid-1,因为左区间不包括这个点本身 //之所以这样写是为了卡常 if(r>mid) rs(p)=build_tree(mid+1,r,tp^1); updata(p);//再次更新节点信息 return p;} 3、修改操作修改操作指的是将在某个范围内的所有节点的权值更改,支持像线段树一样打标记和下传标记具体见代码123456789101112131415161718192021222324252627282930313233343536373839//注:本题要求的是先乘上一个数再加上一个数,所以有两个标记数组inline void Add_mlt(int p,int v){ tree[p].val*=v; tree[p].cnt*=v; tree[p].mlt*=v; tree[p].sum*=v;}inline void Add_cnt(int p,int v){ tree[p].val+=v; tree[p].cnt+=v; tree[p].sum+=tree[p].len*v;}inline void pushdown(int p){ if(tree[p].mlt!=1){//下传乘标记 Add_mlt(ls(p),tree[p].mlt); Add_mlt(rs(p),tree[p].mlt); tree[p].mlt=1; } if(tree[p].cnt!=0){//下传加标记 Add_cnt(ls(p),tree[p].cnt); Add_cnt(rs(p),tree[p].cnt); tree[p].cnt=0; }}inline void change(int p,int x,int y,int mt,int ct){//p为当前节点,将第T维(T为全局变量,记录当前处理维度)坐标在x与y之间的数都乘上mt,加上ct if(tree[p].Max[T]<x||y<tree[p].Min[T])//如果不在要处理的范围内,退出 return ; if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){//如果都在要处理的范围内,就打上乘标记与加标记 Add_mlt(p,mt);//加乘标记 Add_cnt(p,ct);//加加标记 return ; } pushdown(p);//下传标记 if(x<=tree[p].v[T]&&tree[p].v[T]<=y)//如果当前点在处理范围内 tree[p].val=tree[p].val*mt+ct;//处理当前节点 change(ls(p),x,y,mt,ct);//修改左子树 change(rs(p),x,y,mt,ct);//修改右子树 tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;//更新当前节点} 5、查询操作详见注释1234567891011inline int ask(int p,int x,int y){//查询T维x到y的和 if(tree[p].Max[T]<x||y<tree[p].Min[T])//出范围就return 0 return 0; if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y)//在范围之内就返回和 return tree[p].sum; pushdown(p);//下传标记 int res=ask(ls(p),x,y)+ask(rs(p),x,y);//加上左右子树 if(x<=tree[p].v[T]&&tree[p].v[T]<=y) res+=tree[p].val;//加上这个点本身 return res;} 至此,kdtree算法的讲解就到此结束,让我们回到原题容易想到可以吧原题中的i到j看作一维,吧$p_i$到$p_j$看作第二维,这样就可以看作是在一个二维平面上进行操作代码(模板):123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124#include<bits/stdc++.h>#define F(i,j,n) for(register int i=j;i<=n;i++)#define INF 0x3f3f3f3f#define ll long long#define mem(i,j) memset(i,j,sizeof(i))using namespace std;#define Md 536870912int n,m,T;inline int read(){ int datta=0;char chchc=getchar();bool okoko=0; while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();} while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();} return okoko?-datta:datta;}class kd_tree{//之所以用class是为了装逼 private: public: #define ls(p) tree[p].ls #define rs(p) tree[p].rs int tot,rt; struct hahaha{ int tp,ls,rs,v[2],Max[2],Min[2],val; int cnt,mlt,sum,len; bool operator<(const hahaha &y)const{ return v[T]<y.v[T]; } }tree[50010]; inline void Add(int *a,int v){ a[0]=a[1]=v; } inline void updata(int p){ tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0])); tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1])); tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0])); tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1])); } inline int build_tree(int l,int r,int tp){ T=tp; int mid=((l+r)>>1),p=mid; nth_element(tree+l,tree+mid,tree+r+1); tree[p].tp=tp; tree[p].mlt=1; tree[p].len=r-l+1; if(l<mid) ls(p)=build_tree(l,mid-1,tp^1); if(r>mid) rs(p)=build_tree(mid+1,r,tp^1); updata(p); return p; } inline void init(){ Add(tree[0].Max,-INF); Add(tree[0].Min,INF); F(i,1,n) tree[i].v[0]=i,tree[i].v[1]=read(); rt=build_tree(1,tot=n,0); } inline void Add_mlt(int p,int v){ tree[p].val*=v; tree[p].cnt*=v; tree[p].mlt*=v; tree[p].sum*=v; } inline void Add_cnt(int p,int v){ tree[p].val+=v; tree[p].cnt+=v; tree[p].sum+=tree[p].len*v; } inline void pushdown(int p){ if(tree[p].mlt!=1){ Add_mlt(ls(p),tree[p].mlt); Add_mlt(rs(p),tree[p].mlt); tree[p].mlt=1; } if(tree[p].cnt!=0){ Add_cnt(ls(p),tree[p].cnt); Add_cnt(rs(p),tree[p].cnt); tree[p].cnt=0; } } inline void change(int p,int x,int y,int mt,int ct){ if(tree[p].Max[T]<x||y<tree[p].Min[T]) return ; if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){ Add_mlt(p,mt); Add_cnt(p,ct); return ; } pushdown(p); if(x<=tree[p].v[T]&&tree[p].v[T]<=y) tree[p].val=tree[p].val*mt+ct; change(ls(p),x,y,mt,ct); change(rs(p),x,y,mt,ct); tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val; } inline int ask(int p,int x,int y){ if(tree[p].Max[T]<x||y<tree[p].Min[T]) return 0; if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y) return tree[p].sum; pushdown(p); int res=ask(ls(p),x,y)+ask(rs(p),x,y); if(x<=tree[p].v[T]&&tree[p].v[T]<=y) res+=tree[p].val; return res; }}K;int main(){ n=read();m=read(); K.init(); F(i,1,m){ int opt=read(),l=read(),r=read(),x,y; if(opt<=1){ x=read();y=read(); T=opt; K.change(K.rt,l,r,x,y); } if(opt>=2){ T=opt-2; printf("%d\n",K.ask(K.rt,l,r)&(Md-1));//不这么写会T } } return 0;}]]></content>
<tags>
<tag>算法</tag>
</tags>
</entry>
</search>