一些考试题,没想出来的各种贪心dp一类的
库存
这些题目考试时费劲心思没有想出,考完后发现是一个有意思的贪心dp,挺奇妙的,没有接触过,所以记在这里,方便回顾。
数分考试
ZR提高十连测day9 T1 problem440
一共有$n$个人参加了考试。第$i$个人的名次区间是$[L_i,R_i]$。除此之外,又有$m$条其他信息,形如$u_i,v_i$,表示第$u_i$个人考的要比$v_i$好(即排名更低)。是否有一个合法的排名,满足上述所有的要求。如果有,请输出任意一组解,否则输出"-1"(不含括号)。$n \leq 3\times 10^5, m\le 10^6$
分析:
首先建反图拓扑,然后拓扑的过程中,判断是否存在环。用优先队列,每次弹出左端点最靠右的人,左端点相同弹右端点最靠右的,这样首先保证了图上的关系,然后又是尽量靠右的。记录一个Ti,从后往前依次更新第Ti名的人是谁。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 char buf[100000], *p1 = buf, *p2 = buf; 17 #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++) 18 inline int read() { 19 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 20 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 21 } 22 23 const int N = 300005; 24 25 struct Edge{ 26 int to, nxt; 27 Edge() { } 28 Edge(int a,int b) { to = a, nxt = b; } 29 }e[2000005]; 30 struct Node{ 31 int l, r, id; 32 bool operator < (const Node &A) const { 33 return l == A.l ? r < A.r : l < A.l; 34 } 35 }A[N]; 36 int head[N], deg[N], ans[N], n, En; 37 bool vis[N]; 38 priority_queue< Node >q; 39 40 void add_edge(int u,int v) { 41 ++En; e[En] = Edge(v, head[u]); head[u] = En; 42 } 43 bool topo() { 44 int L = 1, R = 0; 45 for (int i = 1; i <= n; ++i) 46 if (!deg[i]) q.push(A[i]); 47 int Ti = n; 48 while (!q.empty()) { 49 Node now = q.top(); q.pop(); 50 if (now.l > Ti || now.r < Ti) return 0; 51 vis[now.id] = 1; 52 ans[Ti --] = now.id; 53 for (int i = head[now.id]; i; i=e[i].nxt) 54 if ((--deg[e[i].to]) == 0) q.push(A[e[i].to]); 55 } 56 for (int i = 1; i <= n; ++i) 57 if (!vis[i]) return 0; 58 return 1; 59 } 60 int main() { 61 n = read(); int m = read(); 62 for (int i = 1; i <= n; ++i) 63 A[i].l = read(), A[i].r = read(), A[i].id = i; 64 for (int i = 1; i <= m; ++i) { 65 int u = read(), v = read(); 66 add_edge(v, u); deg[u] ++; 67 } 68 if (!topo()) { cout << -1; return 0; } 69 for (int i = 1; i <= n; ++i) printf("%d\n",ans[i]); 70 return 0; 71 } 72 /* 73 4 3 74 75 3 4 76 1 3 77 2 4 78 2 3 79 80 2 3 81 3 4 82 2 1 83 */
吃鱼
清北测试题
有n只猫。安排了m条鱼给这些猫吃。第i只猫吃一条鱼需要花费a[i]的时间。一只猫在同一时间最多只会吃一条鱼,且不会有多只猫吃同一条鱼。在第0时刻,给每只猫一条鱼。每当有一只猫吃完鱼时,如果此时还有鱼,它会立刻吃下一条鱼。特别地:如果有k只猫在同一时刻一起吃完了鱼,且此时剩下的鱼的个数不足k,吃的快的猫(即a[i]较小的猫)会优先吃鱼。求经过x个时间后,有多少条鱼还没被吃过,以及有多少鱼已经被吃了一部分了(也就是说还没吃完,但已经被吃过了)。1<=n,m<=100000,1<=a[i],x<=10^9,且m>=n。
分析:
可以直接维护每只猫在那个时刻吃完了,然后扫一下时间即可。因为最多m条鱼,所以可以复杂度最多m。优化这个扫时间的过程,用单调队列维护。每次取吃完的时间最小的,吃的最快的。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 #define pa pair<int,int> 22 #define mp make_pair 23 24 const int N = 100005; 25 26 priority_queue< pa, vector< pa >, greater< pa > > q; 27 28 int main() { 29 30 freopen("fish.in","r",stdin); 31 freopen("fish.out","w",stdout); 32 33 int m = read(), n = read(), x = read(); 34 for (int i = 1, a; i <= n; ++i) 35 a = read(), q.push(mp(a, a)); 36 m -= n; 37 while (!q.empty()) { 38 int now = q.top().first; 39 if (now >= x) break; 40 int ti = q.top().second; q.pop(); 41 if (m == 0) continue; 42 m --; 43 q.push(mp(now + ti, ti)); 44 } 45 while (!q.empty() && q.top().first <= x) q.pop(); 46 47 cout << m << " " << q.size(); 48 return 0; 49 }
01背包威力加强版
清北测试题
有m块钱,总共有n个物品,每个物品有它的价格pi,参数qi,价值vi。LYK可以用某个顺序来购物。它想买一个物品当且仅当它还剩下的钱不小于该物品的参数qi。之后为了买这个物品,它的总钱数会减少pi,相应的,得到的价值总和会增加vi。每个物品最多买一次,LYK可以随便安排一个购物顺序,它想使得满足购物要求的前提下得到的价值之和尽可能高。
1<=n<=1000,1<=m<=5000,1<=p[i]<=q[i]<=5000,1<=v[i]<=5000。
分析:
考虑以什么样的顺序购买。假设当前有9 3,6 5,如果先考虑6 5,那么会从dp[6]->dp[1],dp[7]->dp[2]...在考虑9,3的时候,会从dp[9]->dp[6],dp[10]->dp[7]...dp[1],所以只有先考虑93才行。然后如果有9 3,9 5,m=12,同样发现只有9,5最多更新到dp[7],而9 3可以更新到dp[9],于是为了可以合并,应该先更新大的。于是按q-p从大到小排序,然后01背包。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 const int N = 1005; 22 23 struct Node{ 24 int siz, val, h; 25 bool operator < (const Node &A) const { 26 return h - siz > A.h - A.siz; // A.h - siz???!!!... 27 } 28 }A[N]; 29 LL f[N][5005]; 30 31 int main() { 32 33 freopen("bag.in","r",stdin); 34 freopen("bag.out","w",stdout); 35 36 int n = read(), m = read(); 37 for (int i = 1; i <= n; ++i) { 38 A[i].siz = read(), A[i].h = read(), A[i].val = read(); 39 } 40 sort(A + 1, A + n + 1); 41 42 for (int i = 1; i <= n; ++i) { 43 for (int w = 0; w <= m; ++w) { 44 f[i][w] = f[i - 1][w]; 45 if (w - A[i].siz >= 0 && m - w + A[i].siz >= A[i].h) 46 f[i][w] = max(f[i - 1][w - A[i].siz] + A[i].val, f[i][w]); 47 } 48 } 49 LL ans = 0; 50 for (int i = 0; i <= m; ++i) ans = max(ans, f[n][i]); 51 cout << ans; 52 return 0; 53 } 54 /* 55 2 10 56 1 11 6 57 5 10 5 58 59 3 10 60 1 11 6 61 5 10 5 62 1 4 6 63 */
最长路
https://www.nowcoder.com/acm/contest/178/A
分析:
首先建反图,拓扑。然后拓扑的过程考虑如何比较两条路径的大小,找到它们第一个不同的位置即可,于是可以倍增加hash找到第一个不同的字符,然后比较大小。
另一种奇妙的思路:记录反图拓扑中每一层的点,从小的层数往大的扫,每扫到一个点,在原图中找到它连向的点,确定从哪个点转移。每个点记录一个pair第一关键字为这条边的权值,第二关键字为前面n-1条边合起来的排名。从一层到下一层的时候,假设我们当前知道了当前层的每个点排名r2,然后下一层的排名就是让这一层到下一层的边上的字符作为第一关键字r1,然后对这个pair排序。第一层的pair当然是(w,0),w为与第0层边上的权值。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 #define pa pair<int,int> 22 #define mp make_pair 23 const int N = 1000005; 24 const int mod = 998244353; 25 26 int q[N], deg[N], dis[N], rnk[N], ans[N]; 27 bool vis[N]; 28 vector<int> vec[N]; 29 pa a[N]; 30 struct Edge{ 31 int head[N], to[N], nxt[N], w[N], En; 32 inline void add_edge(int u,int v,int c) { 33 ++En; to[En] = v; w[En] = c; nxt[En] = head[u]; head[u] = En; 34 } 35 }G, R; 36 37 inline bool cmp(int i,int j) { return a[i] < a[j]; } 38 39 int main() { 40 int n = read(), m = read(); 41 for (int i=1; i<=m; ++i) { 42 int u = read(), v = read(), w = read(); 43 G.add_edge(u, v, w); R.add_edge(v, u, w); deg[u] ++; 44 } 45 46 int l = 1, r = 0; 47 for (int i=1; i<=n; ++i) if (!deg[i]) q[++r] = i; 48 49 while (l <= r) { 50 int u = q[l ++]; 51 vis[u] = true; 52 vec[dis[u]].push_back(u); 53 a[u] = mp(2e9, 0); 54 for (int i=R.head[u]; i; i=R.nxt[i]) { 55 int v = R.to[i]; 56 dis[v] = max(dis[v], dis[u] + 1); 57 if (!(--deg[v])) q[++r] = v; 58 } 59 } 60 61 pa tmp; 62 for (int i=1; i<=n; ++i) { 63 if (!vec[i].size()) break; 64 for (int j=0,sz=vec[i].size(); j<sz; ++j) { 65 int u = vec[i][j]; 66 for (int i=G.head[u]; i; i=G.nxt[i]) { 67 int v = G.to[i]; 68 tmp = mp(G.w[i], rnk[v]); 69 if (dis[u] == dis[v] + 1 && tmp < a[u]) 70 a[u] = tmp, ans[u] = (1ll * (ans[v] + a[u].first) % mod * 29) % mod; 71 } 72 } 73 sort(vec[i].begin(), vec[i].end(), cmp); 74 for (int j=0, sz=vec[i].size(); j<sz; ++j) rnk[vec[i][j]] = j; 75 } 76 77 for (int i=1; i<=n; ++i) { 78 if (vis[i]) printf("%d\n",ans[i]); 79 else puts("Infinity"); 80 } 81 82 return 0; 83 }
旅行
zhengruioi 提高组5连测 B, problem 353
给定一棵 $n$ 个点的树,再给定一个长度为 $m$ 的序列 $a_1,a_2...a_m$。你需要对每个 $i\in [1,m]$ 都求出一条最短的起点为 1 ,终点为 $a_i$ 的路径(可以多次重复经过同一点),使得 $a_1,a_2...a_{i-1}$ 都在这条路径上,你只需要输出符合条件的最短的路径上边的数量。
分析:
只有一条链可以走一次,其他的走两次,暴力往上跳,到以前走过的位置就不走了,然后计算答案。
#336. 【18 提高 4】天
洪蛤吨来到了一条有$N$家商店的步行街,商店从左往右依次从$1$标号到$N$。每家商店都买卖同一件产品,且都有同样的规矩:每个来到店里的人,只能买一件产品,卖一件产品,或者什么都不做。有趣的是,在同一家店里买卖产品,支出或者收到的钱是一样的。具体的,如果洪蛤吨在第$i$家店里买/卖一件产品,那么洪蛤吨将会支出/收到$A_i$单位的货币。
洪蛤吨将从$1$号商店依次走到$N$号商店,一开始洪蛤吨有无限的初始资金,但并没有任何一件产品。
问收益最大是多少。在此前提下,进入店内买/卖产品的次数尽量少。 $1 \le T \le 5 $,$1 \le N \le 50000$,$1 \le A_i \le 10^9$。
分析:
每个点只能买或者卖。
第一档:3^n枚举每个点是买/卖/不操作
第二档:状压前面没有买的点。
第三档:dp,f[i][j]表示到第i个位置,买了j个的获益。然后判断i是买/卖/不操作。**个人理解**:第二维在转移时好像并没有用,因为这j个也不知道是哪些,获得的收益也不知道从哪里买的。但是去掉这一维发现不能转移了(转移时不会买,因为一旦买就成了负数了),转移时i只有三种情况买/卖/不操作,为了可以转移,必须要满足三种情况可以转移(从哪转移,贡献怎么算),发现转移会影响当前买的个数,于是加上这个限制条件就可以转移了。
第四档:直接判断第i个位置是否可以卖出。然后从前面所有没有买的找最小的,从所有卖出的位置替换(表示在i这个位置卖),如果都不能获得收益,则只能在i这里买了。注意:为了让操作次数更少,于是先考虑在i卖出,可能代替某些位置。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<queue> 7 using namespace std; 8 typedef long long LL; 9 10 inline int read() { 11 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 12 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 13 } 14 15 const int N = 50010; 16 17 int a[N]; 18 int n; 19 20 priority_queue< int, vector<int>, greater<int> > q1, q2; 21 22 void init() { 23 n = read(); 24 for (int i=1; i<=n; ++i) a[i] = read(); 25 while (!q1.empty()) q1.pop(); 26 while (!q2.empty()) q2.pop(); 27 } 28 29 void solve() { 30 LL Ans = 0; 31 for (int i=1; i<=n; ++i) { 32 int opt = 0, mx = 0; 33 if (!q2.empty()) { // 卖出的,优先考虑,减少购买次数 34 if (mx < a[i] - q2.top()) 35 mx = a[i] - q2.top(), opt = 1; 36 } 37 if (!q1.empty()) { // 未买入的 38 if (mx < a[i] - q1.top()) 39 mx = a[i] - q1.top(), opt = 2; 40 } 41 Ans += mx; 42 if (opt == 0) q1.push(a[i]); 43 else if (opt == 1) q1.push(q2.top()), q2.pop(), q2.push(a[i]); 44 else q1.pop(), q2.push(a[i]); 45 } 46 cout << Ans << " " << (int)(q2.size()) * 2 << "\n"; 47 } 48 49 int main() { 50 int T = read(); 51 while (T--) { 52 init(); 53 solve(); 54 } 55 return 0; 56 }
Poker
1 #include <bits/stdc++.h> 2 #define db double 3 #define uint unsigned int 4 #define ll long long 5 #define ull unsigned long long 6 #define vint vector <int> 7 #define fir first 8 #define sec second 9 #define mkp make_pair 10 #define pb push_back 11 #define pii pair <int, int> 12 #define rep(i, a, b) for (int i = (a), __end = (b); i <= __end; ++i) 13 #define repdown(i, a, b) for (int i = (a), __end = (b); i >= __end; --i) 14 #define RG register 15 #define clr(a) memset(a, 0, sizeof(a)) 16 #define SZ(a) int(a.size()) 17 using namespace std; 18 19 const int mod = 998244353; 20 const int gox[4] = {-1, 1, 0, 0}; 21 const int goy[4] = {0, 0, 1, -1}; 22 23 int iabs(int x) { return x < 0 ? -x : x; } 24 ll labs(ll x) { return x < 0 ? -x : x; } 25 int dec(int x, int v) { x -= v; return x < 0 ? x + mod : x; } 26 int inc(int x, int v) { x += v; return x >= mod ? x - mod : x; } 27 inline int power(ll x, ll l) { 28 ll ret = 1; 29 for (; l; l >>= 1, x = x * x % mod) 30 if (l & 1) ret = ret * x % mod; 31 return ret; 32 } 33 int cmax(int &x, int v) { return x < v ? x = v : x; } 34 int cmin(int &x, int v) { return x > v ? x = v : x; } 35 int bits(int x, int v) { return (x >> v) & 1; } 36 37 const int MaxN = 3010; 38 int a[MaxN], b[MaxN]; 39 int n; 40 struct edge { 41 int u, v, w; 42 bool operator < (const edge &t) const { 43 return w < t.w; 44 } 45 } e[MaxN * MaxN]; 46 47 int uf[MaxN]; 48 int getf(int x) { return x == uf[x] ? x : uf[x] = getf(uf[x]); } 49 50 ll solve() { 51 scanf("%d", &n); 52 rep(i, 1, n) 53 scanf("%d", &a[i]); 54 rep(i, 1, n) 55 scanf("%d", &b[i]); 56 int tot = 0; 57 ll ans = 0; 58 rep(i, 1, n) 59 rep(j, i + 1, n) 60 e[++tot] = (edge){i, j, min(a[i] ^ b[j], a[j] ^ b[i])}; 61 sort(e + 1, e + tot + 1); 62 rep(i, 1, n) uf[i] = i; 63 rep(i, 1, tot) { 64 int u = e[i].u, v = e[i].v; 65 u = getf(u); 66 v = getf(v); 67 if (u != v) { 68 uf[u] = v; 69 ans += e[i].w; 70 } 71 } 72 return ans; 73 } 74 75 int main() { 76 int T; 77 scanf("%d", &T); 78 rep(_, 1, T) { 79 printf("%lld\n", solve()); 80 } 81 return 0; 82 }
Fight
分析:如果将所有攻击力按从大到小排序,那么发现这是一个DAG,所以k=0的时候直接输出0就行了。
n^3比较暴力了,记一下n^2的做法:n次操作,如果每次操作都暴力修改边的话,那么就是n^3了,考虑到每次修改对一个点的正反都是一段区间,所以可以差分。然后求一下三元环可得70分。
正解:正难则反。用总的三元环-不合法的三元环。从大的点开始往小的点扫,扫的过程维护与那些点是有边的(0/1)。从一个较大的点到下一个点的时候,如果没有修改操作的时候,只影响了一对点对的,就是这个点无法到上一个点,其他的点都是可以的,因为这个点是次小的。那么有修改操作的话是一样的。一个操作区间如果只影响了上一个点,那么在这里会被该回去,如果都影响了, 那么它们到达点的状态是一样的。详见代码。
1 /* 2 * @Author: mjt 3 * @Date: 2018-10-28 15:33:22 4 * @Last Modified by: mjt 5 * @Last Modified time: 2018-10-28 15:48:52 6 */ 7 #include<cstdio> 8 #include<algorithm> 9 #include<cstring> 10 #include<cmath> 11 #include<iostream> 12 #include<cctype> 13 #include<set> 14 #include<vector> 15 #include<queue> 16 #include<map> 17 #define fi(s) freopen(s,"r",stdin); 18 #define fo(s) freopen(s,"w",stdout); 19 using namespace std; 20 typedef long long LL; 21 22 inline int read() { 23 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 24 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 25 } 26 27 #define Root 1, n, 1 28 #define lson l, mid, rt << 1 29 #define rson mid + 1, r, rt << 1 | 1 30 #define pa pair<int,int> 31 #define mp make_pair 32 const int N = 100005; 33 34 int a[N]; 35 vector< pa > opt[N]; 36 37 struct SegmentTree{ 38 int sum[N << 2]; bool tag[N << 2]; 39 inline void pushup(int rt) { 40 sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; 41 } 42 inline void pushdown(int rt,int len) { 43 if (tag[rt]) { 44 tag[rt << 1] ^= 1; tag[rt << 1 | 1] ^= 1; 45 sum[rt << 1] = (len - len / 2) - sum[rt << 1]; 46 sum[rt << 1 | 1] = (len / 2) - sum[rt << 1 | 1]; 47 tag[rt] ^= 1; 48 } 49 } 50 void update(int l,int r,int rt,int L,int R) { 51 if (L <= l && r <= R) { 52 sum[rt] = (r - l + 1) - sum[rt]; tag[rt] ^= 1; 53 return ; 54 } 55 int mid = (l + r) >> 1; 56 pushdown(rt, r - l + 1); 57 if (L <= mid) update(lson, L, R); 58 if (R > mid) update(rson, L, R); 59 pushup(rt); 60 } 61 int query(int l,int r,int rt,int p) { 62 if (l == r) { 63 return sum[rt]; 64 } 65 int mid = (l + r) >> 1; 66 pushdown(rt, r - l + 1); 67 if (p <= mid) return query(lson, p); 68 else return query(rson, p); 69 } 70 }T; 71 72 int main() { 73 int n = read(), k = read(); 74 for (int i = 1; i <= n; ++i) a[i] = read(); 75 sort(a + 1, a + n + 1); 76 for (int i = 1; i <= k; ++i) { 77 int l = read(); l = lower_bound(a + 1, a + n + 1, l) - a; 78 int r = read(); r = upper_bound(a + 1, a + n + 1, r) - a; r --; 79 if (l > r) continue; 80 opt[l].push_back(mp(l, r)); opt[r + 1].push_back(mp(l, r)); 81 } 82 T.update(Root, 1, n); 83 LL ans = 1ll * n * (n - 1) * (n - 2) / 6; 84 for (int i = 1; i <= n; ++i) { 85 if (i > 1) T.update(Root, i - 1, i - 1); 86 for (int j = 0, sz = opt[i].size(); j < sz; ++j) 87 T.update(Root, opt[i][j].first, opt[i][j].second); 88 int cnt = T.sum[1] - T.query(Root, i); 89 ans -= 1ll * cnt * (cnt - 1) / 2; 90 } 91 cout << ans; 92 return 0; 93 }
最大不相交路径
分析:
n^2 dp:dp[i]表示以i为根的最大的点权和。那么转移可以从所有的子节点转移(即所有的子节点的dp之和),如果存在一条路径的最近公共祖先是i,那么就可以从x转移,x满足x的父节点在路径上,而x不在路径上。
如果路径是这样的,那么就从下面的绿点转移。
路径是这样的,从下面的绿点转移。
正解:用线段树维护,直接求和。维护两个线段树,第一棵中的每个点记录从这个点往上到根的路径上经过的点的dp值的和。第二棵线段树记录这个点开始往上到根的路径上的点的dp的值(到这里和第一棵线段树是一样的)+这些的所有子节点的和。比如第一张图中,下面的黑点就记录了图中所有的点的dp的和。查询的时候类似差分查询。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 const int N = 100005; 22 const int Log = 17; 23 24 struct Edge{ 25 int to, nxt; 26 Edge() {} 27 Edge(int a,int b) { to = a; nxt = b; } 28 }e[N << 1]; 29 struct Que{ 30 int x, y, z; 31 }q[N]; 32 vector<int> vec[N]; 33 34 int head[N], w[N]; 35 int siz[N], st[N], ed[N], fa[N], f[N][18], deth[N]; 36 int En, Time_Index, n; 37 LL dp[N], sum[N]; 38 39 #define Root 1, n, 1 40 #define lson l, mid, rt << 1 41 #define rson mid + 1, r, rt << 1 | 1 42 struct SegmentTree{ 43 LL s[N << 2]; 44 void update(int l,int r,int rt,int L,int R,LL w) { 45 if (L > r || R < l) return ; 46 if (L <= l && r <= R) { 47 s[rt] += w; return ; 48 } 49 int mid = (l + r) >> 1; 50 if (L <= mid) update(lson, L, R, w); 51 if (R > mid) update(rson, L, R, w); 52 } 53 LL query(int l,int r,int rt,int p) { 54 if (!p) return 0; // !!! 55 if (l == r) return s[rt]; 56 int mid = (l + r) >> 1; 57 if (p <= mid) return query(lson, p) + s[rt]; 58 else return query(rson, p) + s[rt]; 59 } 60 }T[2]; 61 62 void add_edge(int u,int v) { 63 ++En; e[En] = Edge(v, head[u]); head[u] = En; 64 ++En; e[En] = Edge(u, head[v]); head[v] = En; 65 } 66 67 void dfs1(int u) { 68 sum[u] = sum[fa[u]] + w[u]; 69 st[u] = ++Time_Index; siz[u] = 1; 70 for (int i = head[u]; i; i=e[i].nxt) { 71 int v = e[i].to; 72 if (v == fa[u]) continue; 73 fa[v] = f[v][0] = u; deth[v] = deth[u] + 1; 74 dfs1(v); 75 siz[u] += siz[v]; 76 } 77 ed[u] = Time_Index; 78 } 79 int LCA(int u,int v) { 80 if (deth[u] < deth[v]) swap(u, v); 81 int d = deth[u] - deth[v]; 82 for (int i = Log; i >= 0; --i) 83 if ((d >> i) & 1) u = f[u][i]; 84 if (u == v) return u; 85 for (int i = Log; i >= 0; --i) 86 if (f[u][i] != f[v][i]) u = f[u][i], v = f[v][i]; 87 return f[u][0]; 88 } 89 int gogo(int u,int v) { 90 for (int i = Log; i >= 0; --i) 91 if (deth[u] - (1 << i) > deth[v]) u = f[u][i]; 92 return u; 93 } 94 95 LL getchain(int x,int y) { 96 return T[1].query(Root, st[x]) - T[1].query(Root, st[fa[y]]) - 97 (T[0].query(Root, st[x]) - T[0].query(Root, st[y])); 98 } 99 LL Calc(int x,int y,int z) { 100 if (deth[x] < deth[y]) swap(x, y); 101 if (y == z) return getchain(x, y) + sum[x] - sum[fa[y]]; 102 int t = gogo(y, z); 103 return getchain(x, z) + getchain(y, t) - dp[t] + sum[x] + sum[y] - sum[z] - sum[fa[z]]; // (x, z)!!! 104 } 105 void dfs2(int u) { 106 dp[u] = 0; 107 for (int i = head[u]; i; i = e[i].nxt) 108 if (e[i].to != fa[u]) dfs2(e[i].to), dp[u] += dp[e[i].to]; 109 for (int sz = vec[u].size(), i = 0; i < sz; ++i) { 110 int t = vec[u][i], x = q[t].x, y = q[t].y, z = q[t].z; 111 dp[u] = max(dp[u], Calc(x, y, z)); 112 } 113 T[0].update(Root, st[u], ed[u], dp[u]); 114 T[1].update(Root, st[fa[u]], ed[fa[u]], dp[u]); 115 } 116 117 int main() { 118 n = read(); 119 for (int i = 1; i <= n; ++i) w[i] = read(); 120 for (int i = 1; i < n; ++i) { 121 int u = read(), v = read(); 122 add_edge(u, v); 123 } 124 dfs1(1); 125 for (int j = 1; j <= Log; ++j) 126 for (int i = 1; i <= n; ++i) f[i][j] = f[f[i][j - 1]][j - 1]; 127 int m = read(); 128 for (int i = 1; i <= m; ++i) { 129 q[i].x = read(), q[i].y = read(), q[i].z = LCA(q[i].x, q[i].y); 130 vec[q[i].z].push_back(i); 131 } 132 dfs2(1); 133 cout << dp[1]; 134 return 0; 135 } 136 /* 137 5 138 1 2 3 4 5 139 1 2 140 1 3 141 2 4 142 2 5 143 3 144 2 4 145 2 5 146 5 3 147 */
nowcoder 练习赛28 F 颓红警
https://ac.nowcoder.com/acm/contest/200/F
1 /* 2 首先贪心的选越靠上的越好,直到这个它变成0。 3 那么只要处理出来一个点进行攻击多少次,然后把这些次操作更新给子节点就行了。 4 暴力更新是n^2的。考虑如果让一个节点快速的从父节点继承信息。 5 对于一个伤害,从x到y,p - (dep[y] - dep[x])^2 6 p - dep[y]^2 + 2*dep[y]*dep[x] - dep[x]^2 7 维护dep[y]^2和dep[y]的系数,如果有多个伤害的话,就可以合并了。 8 A*dep[y]^2 + B*dep[y] + C 9 如果是从一个点出发的多个伤害,那么就是系数上乘以一个cnt就好了。 10 如果是从不同的点出发的伤害,那么就是A增加了,dep[x]变了,更新相应的系数 11 */ 12 13 #include<cstdio> 14 #include<algorithm> 15 #include<cstring> 16 #include<iostream> 17 #include<cctype> 18 #include<cmath> 19 #include<set> 20 #include<queue> 21 #include<vector> 22 #include<map> 23 using namespace std; 24 typedef long long LL; 25 26 int read() { 27 int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1; 28 for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f; 29 } 30 31 const int N = 1000005; 32 33 struct Edge{ 34 int to, nxt; 35 }e[N]; 36 int head[N], s[N], w[N], deth[N], n, p, d, En; 37 LL A[N], B[N], C[N], cnt[N], Ans; 38 39 void add_edge(int u,int v) { 40 ++En; e[En].to = v, e[En].nxt = head[u]; head[u] = En; 41 } 42 43 void dfs(int u,int dep) { 44 s[dep] = u; 45 if (dep >= d) { // x无法对u造成伤害的点 46 int x = s[dep - d], dx = dep - d; 47 A[u] -= -cnt[x]; 48 B[u] -= 2 * cnt[x] * dx; // 2 * cnt[i] * dep[i] 49 C[u] -= cnt[x] * (p - 1ll * dx * dx); 50 } 51 LL r = w[u] - (A[u] * dep * dep + B[u] * dep + C[u]); 52 if (r < 0) cnt[u] = 0; 53 else cnt[u] = r / p + 1; 54 Ans += cnt[u]; 55 for (int i = head[u]; i; i = e[i].nxt) { 56 int v = e[i].to; 57 A[v] = A[u] - cnt[u]; 58 B[v] = B[u] + 2 * cnt[u] * dep; 59 C[v] = C[u] + cnt[u] * (p - 1ll * dep * dep); 60 dfs(v, dep + 1); 61 } 62 } 63 64 int main() { 65 n = read(), p = read(); d = sqrt(p) + 1; // 一个点可以伤害sqrt(p)个点,+1是到达这个点的上方,是第一个伤害不到他的点 66 for (int i = 1; i <= n; ++i) w[i] = read(); 67 for (int i = 1; i < n; ++i) { 68 int u = read(), v = read(); 69 add_edge(u, v); 70 } 71 dfs(1, 0); 72 cout << Ans; 73 return 0; 74 }