Link
Solution
裸,练模板。。
Code
1 | //Code by Lucida |
裸,练模板。。
1 | //Code by Lucida |
神题。。(哦应该是因为我太弱了。。)
一直不明白DFS序到底是怎么定义的
那种记录每个点第一次访问的位置的,总共n个点,有人叫它DFS序
还有那种记录DFS的路径的,总共2n−1个点,有人也叫它DFS序。。
于是各种题解随便一混用就能把我转很久(应该是我太弱了)
好吧,这个用的是第二种DFS序。。那我叫它multiDFS序好了。
首先求出树的multiDFS序=S
那么求点对距离就是求出LCA然后计算,即为区间最小值
然后求最后一个深度为dep的节点,只要在序列上二分然后判存在性就行了,判存在的条件是。不要漏掉第二个条件!!
要求把一棵子树移动到它的第k个祖先上,首先这个第k个祖先也可以在序列上二分得到,而移动在S中就对应区间的移动。转化为的基本操作。
这样两个二分就转化为在上走。
But。。
says ydc

的区间操作一直让我感到云里雾里。 普通的,一般是自顶向下操作(如维修数列),需要一个来在中定位。这样,就可以一路下去,没有问题。 但是在一些直接定位到节点的应用中,比如,它祖先的标记就访问不到了。 学习的时候,看到Po姐的做法是一直递归到根节点,然后一路下放标记。因为也没学会势能分析,也不知道这样复杂度是否有保障,但也就一直这样写了。 在Summer Camp上问了下gty大神,他表示他一直只是在的时候下放一下标记,从来没出现问题。问起关于祖先的标记,他表示没有考虑过。 向gty大神要来代码,最后也没有完全弄明白。 那天晚上跑到省队的实验室去问swh(之前他学的也是类似的写法,只是写hzwer的代码,用手工栈),他说因为操作以后会把节点到根。当时觉得挺有道理的样子,但也没再深究。 写这道题,又用到了直接定位节点需要处理祖先标记的问题。 思考了以下,发现只要按照ydc所讲的,在一个点停下就上去就行了。 在中,需要把父节点的标记下传,然后把当前节点的标记下传,遵循的原则就是:在出子树之前下传防止多传给其它子树,在进入子树之前下传让防止漏传给其它子树。 对于,读取之前本来是可以不需要到根的。(修改也可以在之后) 但现在的话就对读写都一视同仁,都到根,就解决了标记的问题,也让思维更清晰一些。 (特殊情况:提取区间之后对区间进行修改,不能到跟节点,那就修改之后再) 切记对节点进行修改之后要更新区间信息!
1 | //Code by Lucida |
Limak is a little bear. He is currently planning a walk on a rectangular grid. Each cell of the grid is either empty or it contains a single candy. Empty cells are denoted '.', cells with a candy are denoted '#'. Limak wants to go from the top-left corner to the bottom-right corner of the grid. Since he loves sweets, there should be a candy in each cell he visits, including the starting and the final cell. He can only move in the four cardinal directions. In other words, he can move from cell A directly to cell B if and only if A and B share a side. You are given the vector
t: the current state of the grid. Limak's journey may be impossible on the current grid. Your task is to make it possible by moving as few candies as you can. Moving a candy means picking it up and placing it into any empty cell. Return the smallest number of candies that have to be moved in order to make Limak's journey possible. If there is no solution, return -1 instead.
好吧,又不会做了, 棋盘上可以从左上到右下DP。 然而这个是各个方向都能走的,所以好像可以求最短路?但也不会建图 发现正解是按照走的步数划分阶段的 这个很厉害啊,棋盘上的最优新解法get。 还有一点,这个题拿哪些不需要管,只要走就行了,最后肯定能构造出一组可行解来
1 | //Code by Lucida |
Bear Limak has three boxes arranged in a row. The first box currently contains a candies, the second one contains b candies, and the third one contains c candies. Limak thinks that the three boxes would look nice if they had the following two properties: Each box should contain at least one candy. The numbers of candies should form a strictly increasing sequence. In other words, the first box should contain fewer candies than the second box, and the second box should contain fewer candies than the third one. Limak can only modify the current content of the boxes in one way: he can eat some of the candies. You are given the ints a, b, and c. Compute and return the smallest possible number of candies Limak should eat in order to make his three boxes look nice. If he has no way to make his boxes look nice, return -1 instead.
1 | struct ThreeIncreasing |
Little Limak wants to show his parents that he's responsible and independent. He decided to move out and live on his own, at least for some time. While living alone, he has to eat every day. Living alone comes with some other expenses as well. More precisely, Limak will eat 1 fruit and spend x dollars each day he lives on his own. Currently Limak has f fruits and d dollars. Limak can buy more pieces of fruit in the local store. The store sells 1 fruit for p dollars, and Limak can purchase arbitrarily many pieces of fruit there. You are given the ints x, f, d, and p. Compute and return the largest possible number of days Limak can live on his own.
1 | typedef long long ll; |
The lotteries you know are probably quite boring. You just buy a lottery ticket with some numbers and then you hope that the organizers announce the same set of numbers. As you will discover below, the lottery in Bearland is way more fun! In the Bearland lottery, you win if you have a ticket that matches the goal string. The goal string is a string of length N chosen by the organizers. Each character of the string will be 'A', 'B', or 'C'. Additionally, it is guaranteed that the goal string will contain "ABC" as a subsequence. For example, "AABAC" and "BBABC" are two possible goal strings of length 5, but "CBAAA" is not a goal string. However, the lottery tickets do not contain any strings at all. Instead, each Bearland lottery ticket contains some sequence of N (not necessarily distinct) integers. You win the lottery if you can transform your sequence into the goal string. Transformation rules: Each element of the sequence should be replaced by a single character: 'A', 'B', or 'C'. All occurrences of the same number must be replaced by the same letter. For example, the sequence {5, 8, 5, 2} can be transformed into "ABAC" or "BABB" but not into "ABBB". Limak got a lottery ticket for his birthday. You are given the vector
t: the sequence of the N numbers on the the ticket. Count the number of different goal strings for which Limak can win the lottery. Return that count modulo 10^9+7.
好吧这就不会做了,,太弱了,一开始还把题都看错了。。
对一个数字序列,相同的数字替换成A,B,C中相同的字母,问有多少种方案可以让产生的字符串中含有子序列A...B...C。
首先枚举三个不相同的a[i],a[j],a[k],考虑钦定这三个分别为A,B,C对答案能产生的贡献。
对于x..x..x..y.y...z...zz这种情况好说,只要确保当前枚举的x1,y1,z1是同类数字中的第一个,即x1之前没有x,x1与y1之间没有y,依次类推。
那对于ijkxyz ixjkyz之类的情况,可能换出ABCABC AABCBC,只能算一次。这样就限定当前枚举的ABC左边不存在ABC,就可以了。
1 | #include<bits/stdc++.h> |
由于过于蒟蒻,文中的一切结论均不会证明
上图来自srOSengxianOrz的博客
一些说法是自己的理解,可能有些naive,还望指教 写的很Low,大白话,省略了很多概念 可以在这里看到更高大上一些的解说
给定目标函数y=∑cixi 和若干限制形如∑aijxj≤bi 要求
根据数学必修五的解题经验,可以发现最优解一定出现在可行域的边界或者顶点。 单纯形的做法,就是不停地变换整个线性规划让取值从一个顶点走到令一个顶点,直到可以判定当前解最优为止。
所谓标准型,即把约束条件写成形如∑aijxj≤bi 所谓松弛型,即加入一个松弛变量x′,把约束调节写成xi′=bi−∑aijxj,xi′≥0 标准型转化到松弛型是很显然的,非标准型也容易通过同乘−1什么的转化到标准型。 可以发现,对于一个松弛型,当bi均≥0的时候,令xi′=bi,其它变量都为0,就是一组可行解(而对于存在bi<0,可以通过一些构造方法生成一组可行解)。所以接下来就在这个可行解的基础上逼近最优解。
y=∑cixi,可以发现,如果变换之后,仍然有某个ci>0,那就有可能通过修改xi的值让y增大。当所有ci<0,即可以证明线性规划已经达到了最优解。。Pivot就是修改某个值之后相应变换整个线性规划的过程。
对于选定出的e使得ce>0,需要在满足条件的情况下。 那就遍历每个式子xi′=bi−∑aijxj,xi′≥0,如果aie≥0,说明这个式子对xe产生了约束,在这个式子中,xe取最大值的情况就是其它变量为0,使得aiexe=bi。因此用aiebi衡量限制的松紧。找出对这个变量限制最紧的式子,把所有bi都分给它,式子变形为,即 。 此时在xe位置上的那个变量就已经不是xe,而是xi′,所以需要将这个式子带到所有其它的式子里面,相当于把xe用它对应的新式子等效地替代掉。 举个栗子(用′表示变换之后的量) 带入另一个式子k 化简以下就出来了。 相应地,c[]数组也要修改。因为也要把xi′′向y里面带,带完之后所有项的系数都变了。 这样,单纯形就解完了。
是这样说的: 线性规划 ∑aijxj≤bi 的对偶问题是 ∑ajixi′≥cj 两者具有相同的最优解
比如算法导论上给出了一个栗子 满足 x1+x2+3x3≤30 2x1+2x2+5x3≤24 4x1+x2+2x3≤36 x1,x2,x3≥0 的对偶问题是 满足 y1+2y2+4y3≥3 y1+2y2+y3≥1 3y1+5y2+2y3≥2 y1,y2,y3≥0
有了这个工具,一些情况下就可以避免乘−1使bi出现负值了。
轻车熟路,独立A掉,Cool!
可以注意到,每个点对答案的贡献取决于它到它的祖先中离它最近的伐木场的距离。按照套路,设计状态f[i][j][d]表示在以i为根的子树内,建了j个伐木场,i点到最近的伐木场距离为d,这棵子树对答案的最小贡献。
笼统地说d是距离,但是直接表示是不好表示的。所以就做个转化,相当于离散化,用d表示最近的伐木场的深度,这样也可以很方便地转移。
于是考虑f[i][j][d]的转移。
通过观察,可以得到一个性质:一个点p,与它的任意子节点u,要么d相同,要么d[u]==depth[u],即u上建了伐木场。你说这个还叫性质?不是一眼秒的东西嘛。好吧当我没说
然后就可以分情况讨论转移了。
然后这里又涉及背包的合并,我想当然地写完,发现输出0。
最后发现,一开始设置的合法状态的0和任何的值取min都是它本身。苦思冥想得不到结果,最后得出的结论是要求用最小代价把背包填满的多重背包不能用滚动数组。。(如果可以的话还请指教)
1 | //Code by Lucida |
将最大收益转化成最小损失,在转移的时候将损失提前计算,这样DP即可。
1 | //Code by Lucida |
做了大概一年的样子
因为任何站都可以到达1,所以这个图的形态一定是一个包含1的环上挂着很多树。
考虑环,
V1=C1+kV3
V2=C2+kV1
V3=C3+kV2
带进去最后得到
V1−k3V1=C1+kC2+k2C3,
可以看出每个点对答案的贡献与到1的距离成正相关,且V1的值是和环的大小有关的。所以通过枚举修改环上点的后继为1,就可以得到确定环的大小,并且把这个图转化成一棵树,可以进行DP了(在建图的时候直接忽略1的后继,因为忽略了才是树,而且从上式可以看出忽略也没影响)。
然后就转化成一个树上的问题:
可以修改一个点的后继为1,使得∑kdist(1,i)ci最大。
然后就开始高能了。
设计状态f[i][j]表示在以i为根的子树中修改j次,子树中的点对答案的最大贡献。
假如说现在要修改一个点的后继为1,有如下的情况:

明显对答案的影响是不一样的。这个状态没法转移。
于是f[i][j][S]表示在以i为根的子树中修改j次,其子孙节点的修改状态为S,子树中的点对答案的最大贡献。
发现这个问题的特点:当前状态的转移方法会对之后的状态的转移产生影响,而将当前状态的转移方法进行状压又不现实。
于是观察答案的性质:每个点对答案的贡献是kdist(1,i)ci,也就是说,只要dist(1,i)知道了,这个点对答案的贡献就可以算了。所以就考虑把状态加上一维:f[i][j][d]表示在以i为根的子树中修改j次,dist(1,i)==d的情况下,子树中的点对答案的最大贡献。
这样以来,每个点对答案的影响就可以独立统计了,也就解决了当前转移对后面状态转移产生影响的问题。
现在考虑如何转移f[i][j][d](我做的时候把不合法的状态都设置了−∞..)
首先定义1的深度dep=0
对于dep>1的点,有两种选择:修改自己和不修改自己
如果修改自己,那子节点如果不修改,深度就是2,子节点如果修改,深度就是1;
如果不修改自己,子节点如果不修改,深度就是dep+1,如果修改,深度就是1。
所以
然后dep==1和==0也类似,就可做了。
以下纯属一个蒟蒻的自言自语
当dep>1,
对于自己不修改的情况
f[pos][0][dep]肯定是合法的,置为0。
当在这棵子树中花费为0的时候,任意的f[pos][0][d],d≤dep都是合法的(祖先可能被修改)。
而对于自己有修改的情况,只需要把f[pos][1][1]置为0即可。
如果正向推不出来,可以反过来思考:
对于可能出现的每种合法转移,都必须能够沿着它的转移路线走到一个0。
对于自己不修改的情况,对于不同的d,f[pos][][d]的转移是互相独立的,所以在每种d上都需要有一个0。
对于自己有修改的情况,最终追溯到最优的一定是先用1次修改来把自己接到1上。
↑其实这么做完全是作茧自缚,网上遍地是全都置0,在转移的时候走合法路径的代码。。
有N种物品和一个容量为V的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
1 | for(int i=1;i<=n;i++) |
对于01背包和完全背包,可以使用滚动数组优化空间复杂度。
1
2
3
4for(int i=1;i<=n;i++)
for(int v=V;v>=c[i];v--)
//for(int v=c[i];v<=V;v++)
chkmx(f[v],f[v-c[i]]+w[i]);
而对于多重背包,使用滚动数组必须要保证:对于同一个体积v,不会同时被k1和k2个相同的物品同时优化。
即,不能在一个体积的背包里先装上k1个A,然后又装上k2个A。
实现的方法是
1
2
3
4for(int i=1;i<=n;i++)
for(int v=V;~v;v--)
for(int k=0;k*c[i]<=v && k<=cnt[i];k++)
chkmx(f[v],f[v-k*c[i]]+k*w[i]);
对于一般的多重背包,第三层循环的枚举顺序应该可以倒过来(我没想出反例的样子?)
但是对于这道题目就不行了。
因为这个题目在k==0的时候也可能会对答案产生贡献,如果从大到小枚举,就会让答案多加了f[u][0][d+1/1](不要问我怎么知道的)
所以不管怎样写成从小到大的还是保险一些。(也许这样写也有坑点?)
1 | //Code by Lucida |
没有表示出状态,因为叶节点的不会记录,而叶节点的状态对后来的转移也是产生影响的。 之前也做过涉及到未来费用的题目,但是远远没有这个毒瘤。
可以看出两种情况是对称的。找一下它们的共同点:同0/2 不同 1
进而可以发现一个点对对答案产生的贡献是可以分开统计的:
当nA<nB时,每个A都会对答案产生一倍的贡献;
当nB≥nB时,每个B都会对答案产生一倍贡献。
这个转换是明显等价的,这样就把计算过程大大简化了,由计算点对变成了计算点。
那么,对于一个点u,如果它任意一个祖先点p的nA和nB的大小关系确定了,那么u对p的贡献也就确定了。
而祖先的这种大小关系可以用一个二进制串S表示。由此,设计状态如下
f[i][nA][S]表示在以i为根的子树中有nA个A节点,i的所有祖先状态为S的时候,子树子树中的点对答案产生的最小贡献。
转移基本就和普通的树形DP相同了。
但是这样会MLE:nA的上界是2n,S的上界是111...111,n−1个1,所以也是O(2n)级别。
然而,可以发现nA和S是互相制约的,深度越浅的点,子树中的节点越多,但是到祖先的路径越短、状态总数越少。于是,可以把nA和S这两维压到一起存储。
所以这个题就呵呵了
如果有谁A掉了这个题,万望解答我在注释中的疑问
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//Code by Lucida
#include<bits/stdc++.h>
#define get(x) scanf("%d",&x)
//#define debug(x) std::cout<<#x<<'='<<x<<std::endl
template <class T> inline bool chkmx(T &a,const T &b){return a<b?a=b,1:0;}
template <class T> inline bool chkmn(T &a,const T &b){return a>b?a=b,1:0;}
const int INF=0x7f7f7f7f,MAXN=11,A=0,B=1;
int lca(int x,int y)
{
while(x!=y) (x>y?x:y)>>=1;
return x;
}
int sum(int x,int y){return x==INF||y==INF?INF:x+y;}
int dep[1<<MAXN],init[1<<MAXN],pay[1<<MAXN],flow[1<<MAXN][1<<MAXN],n,sumc[1<<MAXN][MAXN],f[1<<MAXN][1<<MAXN<<1];
void DP(int pos)//定义路由器的颜色为数量少的颜色
{
int id=pos-(1<<n)+1,divider=n-dep[pos]+1,covers=(1<<divider>>1);
if(dep[pos]==n)
{
for(int S=0;S<=(1<<dep[pos])-1;++S)//总量为1是A 0是B
{
f[pos][(init[id]^1)+(S<<divider)]=pay[id];
f[pos][init[id]+(S<<divider)]=0;
for(int i=dep[pos]-1;~i;i--)
if(((S>>i)&1)==A) f[pos][1+(S<<divider)]+=sumc[id][i];
else f[pos][0+(S<<divider)]+=sumc[id][i];
}
}
else
{
int ls,rs,sonD=divider-1;DP(ls=pos<<1),DP(rs=pos<<1|1);
for(int fl=0;fl<=(covers>>1);fl++)
for(int fr=0;fr<=(covers>>1);fr++)
{
for(int S=0;S<=(1<<dep[pos])-1;++S)
{
int kind;
//f[i][j][k]表示在以i节点为根的子树,有j个A,祖先状态为K的最小代价
//把第二第三维压成(j+(k<<UPLIM)
if((fl+fr)<=(covers-fl-fr)) kind=A;
else kind=B;
//这里判断当前节点的类型
//如果改成<会WA
int sonS=S|(kind<<dep[pos]);
//int sonS=S|(((fl+fr)>(covers-fl-fr))<<dep[pos]);
chkmn(f[pos][fl+fr+(S<<divider)],sum(f[ls][fl+(sonS<<sonD)],f[rs][fr+(sonS<<sonD)]));
}
}
}
}
int main()
{
get(n);
dep[0]=-1;
for(int i=1;i<=(1<<n<<1)-1;i++) dep[i]=dep[i>>1]+1;
for(int i=1;i<=(1<<n);i++) get(init[i]);
for(int i=1;i<=(1<<n);i++) get(pay[i]);
for(int i=1;i<=(1<<n)-1;i++)
for(int j=i+1;j<=(1<<n);j++)
get(flow[i][j]);
for(int i=1;i<=(1<<n)-1;i++)
for(int j=i+1;j<=(1<<n);j++)
{
int d=dep[lca(i+(1<<n)-1,j+(1<<n)-1)];
sumc[i][d]+=flow[i][j],sumc[j][d]+=flow[i][j];
}
memset(f,0x7f,sizeof(f));//sizeof(127) heheda
DP(1);
int Ans=INF;
for(int i=0;i<=(1<<n);i++)
chkmn(Ans,f[1][i]);
printf("%d\n",Ans);
return 0;
}
/* AC Record(Bugs) *
*
* * * * * * * * * */
模板题了
各种卡终于卡到了RANK1。。感觉自己好闲好zz。。
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//Code by Lucida
#include<bits/stdc++.h>
//#define get(x) scanf("%d",&x)
//#define debug(x) std::cout<<#x<<'='<<x<<std::endl
template <class T> inline bool chkmx(T &a,const T &b){return a<b?a=b,1:0;}
template <class T> inline bool chkmn(T &a,const T &b){return a>b?a=b,1:0;}
namespace IO
{
#ifndef get
const int IN=1e7;
char inbuf[IN],*iptr=inbuf,*iend=inbuf;
#define getc() ((iptr==iend && (iend=(iptr=inbuf)+fread(inbuf,1,IN,stdin),iptr==iend))?EOF:*iptr++)
inline void get(int &x)
{
static int f;static char ch;
f=1;while(!isdigit(ch=getc())) if(ch=='-') f=-1;
x=ch-'0';while(isdigit(ch=getc())) (x*=10)+=ch-'0';
x*=f;
}
#endif
const int OUT=1e7;
char outbuf[OUT],*optr=outbuf,*oend=outbuf+OUT-1;
#define flush() (fwrite(outbuf,1,optr-outbuf,stdout))
#define putc(x) ((optr==oend)?flush(),optr=outbuf,*optr++=x:*optr++=x)
inline void put(int x)
{
if(x<0) putc('-');
if(!x) putc('0');
if(x)
{
static char stack[100];int top;
for(top=0;x;x/=10)
stack[++top]=(x%10)+'0';
while(top) putc(stack[top--]);
}
putc('\n');
}
}using namespace IO;
const int MAXN=400000+10;
namespace iTrie
{
const int LEN=24,SIZE=LEN*MAXN<<1;
struct Node
{
Node *son[2];
int cnt;
Node(){son[0]=son[1]=0;cnt=0;}
}*POOL=(Node*)malloc(SIZE*sizeof(Node)),*ME=POOL,*null=new(ME++) Node,*root[MAXN<<1]={null};
int sum[MAXN<<1],rc;
Node *newNode()
{
static Node *cur;cur=ME++;
cur->son[0]=cur->son[1]=null;cur->cnt=0;
return cur;
}
Node *newNode(Node *old)
{
static Node *cur;cur=new(ME++) Node;*cur=*old;
return cur;
}
void Insert(Node *&pos,int x)
{
pos=newNode(pos);Node *cur=pos;
for(int c,i=LEN-1;~i;i--)
{
c=(x>>i)&1;cur->cnt++;
cur->son[c]=newNode(cur->son[c]);
cur=cur->son[c];
}
cur->cnt++;
}
void Add(int x)
{
++rc;sum[rc]=sum[rc-1]^x;
Insert(root[rc]=root[rc-1],sum[rc]);
}
int cnxt(Node *pl,Node *pr,int c){return pr->son[c]-pl->son[c];}
int Query(int x,int l,int r)
{
int res=x;Node *pl=root[l],*pr=root[r+1];
for(int c,i=LEN-1;~i;i--)
{
c=(x>>i)&1;
if(cnxt(pl,pr,c^1))
{
pl=pl->son[c^1],pr=pr->son[c^1];
res|=(1<<i);
}
else
{
pl=pl->son[c],pr=pr->son[c];
res&=~(1<<i);
}
}
return res;
}
}using namespace iTrie;
int main()
{
null->son[0]=null->son[1]=null;
Add(0);
int n,m;get(n),get(m);
for(int i=1;i<=n;i++)
{
int x;get(x);
Add(x);
}
for(int i=1;i<=m;i++)
{
char opt;int l,r,x;
while(!isalpha(opt=getc()));
if(opt=='A')
{
get(x);
Add(x);
}
else
{
get(l),get(r),get(x);
//printf("%d\n",Query(x^sum[rc],l-1,r-1));
put(Query(x^sum[rc],l-1,r-1));
}
}
flush();
return 0;
}
/* AC Record(Bugs) *
*
* * * * * * * * * */