noip2021訓練2(USACO)
[USACO10MAR] Great Cow Gathering G
換根 dp 板子
先用樹形 dp 求出以 \(1\) 為根的最小不方便值,然後 bfs 向兒子換根
\(f_v=f_u + (siz_u - siz_v \times 2) \times w\)
這個手推一下就好了,大概就是把兩邊子樹中的點經過 \((u,v)\) 這條邊的代價算一下。
Code
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5 + 5; int n, c[N]; struct edge { int v, w, nxt; }e[N << 1]; int head[N], tot; void add(int u, int v, int w) { e[++tot] = (edge) {v, w, head[u]}; head[u] = tot; } ll f[N], siz[N], ans; void dfs(int u, int fa) { siz[u] = c[u]; for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].v; if(v == fa) continue; dfs(v, u); siz[u] += siz[v]; f[u] += f[v] + siz[v] * e[i].w; } return; } queue <int> que; bool vis[N]; void bfs() { ans = 1e18; que.push(1); while(!que.empty()) { int u = que.front(); que.pop(); if(vis[u]) continue; vis[u] = 1; ans = min(ans, f[u]); for(int i = head[u]; i; i = e[i].nxt) { int v = e[i].v; f[v] = f[u] + (siz[u] - siz[v] * 2) * e[i].w; siz[v] = siz[u]; que.push(v); } } return; } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &c[i]); for(int i = 1, u, v, w; i < n; i++) { scanf("%d%d%d", &u, &v, &w); add(u, v, w), add(v, u, w); } dfs(1, 0); bfs(); printf("%lld\n", ans); return 0; }
[USACO09FEB] Stock Market G
CSP-J2019 T3 原題
感覺思路還是比較難的,雖然程式碼難度極低
要求最終能獲得的最大錢數,其實可以分別對每一天,求當天能獲得的最大錢數。
對於每個物品在每天,有三種情況:
- 不買
- 今天買,明天賣
- 今天買,過幾天賣
其實第三種可以通過第二種實現
今天買了,明天賣掉,然後明天再買,後天賣掉...
就相當於今天買了,過幾天再賣。
所以就兩種情況,然後就是一個完全揹包了
設 \(f_i\) 表示手上有 \(i\) 元錢時最大收益,注意總錢數要每天更新。
Code
#include <bits/stdc++.h> using namespace std; const int MAXN = 5e5 + 5; int n, m, s, p[55][15]; int f[MAXN]; int main() { scanf("%d%d%d", &n, &m, &s); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &p[i][j]); for(int j = 2; j <= m; j++) { int mx = 0; memset(f, 0, sizeof(f)); for(int i = 1; i <= n; i++) { for(int k = p[i][j - 1]; k <= s; k++) { f[k] = max(f[k], f[k - p[i][j - 1]] + p[i][j] - p[i][j - 1]); mx = max(mx, f[k]); } } s += mx; } printf("%d\n", s); return 0; }
USACO13MAR Necklace G
這裡用的 AC自動機,也可以用 kmp,但如果有多個不能出現的子串就不行了。
正難則反,求出最多能保留多少,然後減一下就行了
先建出 AC自動機,在上面 dp
設 \(f_{i,j}\) 表示在第 \(i\) 個位置,在 AC自動機上走到了 \(j\) 時最多能保留多長
如果 \(j\) 不是子串的末尾,\(f_{i,j}=max(f_{i,j},f_{i-1,j})\)
令 \(k=trie_{j,a_i}\),也就是在 AC自動機上從 \(j\) 向後沿著 \(a_i\) 走到哪個點。
如果 \(k\) 不是子串的末尾,\(f_{i,k}=max(f_{i,k},f_{i-1,j}+1)\)
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
char a[N], b[N];
int n, m, nxt[N];
int trie[N][30], tot, ed;
int fail[N];
int f[N][1010];
void insert(char s[])
{
int len = strlen(s + 1), x = 0;
for(int i = 1; i <= len; i++)
{
int c = s[i] - 'a';
if(!trie[x][c]) trie[x][c] = ++tot;
x = trie[x][c];
}
ed = x;
return;
}
void build()
{
queue <int> que;
for(int i = 0; i < 26; i++)
if(trie[0][i]) que.push(trie[0][i]);
while(!que.empty())
{
int x = que.front();
que.pop();
for(int i = 0; i < 26; i++)
if(trie[x][i]) fail[trie[x][i]] = trie[fail[x]][i], que.push(trie[x][i]);
else trie[x][i] = trie[fail[x]][i];
}
return;
}
int main()
{
scanf("%s%s", a + 1, b + 1);
n = strlen(a + 1), m = strlen(b + 1);
insert(b), build();
for(int i = 1; i <= n; i++)
for(int j = 0; j <= m; j++)
{
if(j != ed) f[i][j] = max(f[i][j], f[i - 1][j]);
int k = trie[j][a[i] - 'a'];
if(k != ed) f[i][k] = max(f[i][k], f[i - 1][j] + 1);
}
int ans = 0;
for(int i = 0; i <= m; i++)
ans = max(ans, f[n][i]);
printf("%d\n", n - ans);
return 0;
}
USACO12DEC Running Away From the Barn G
神奇的樹上差分
因為只求子樹內的,所以第 \(i\) 個點的貢獻只在這個點到根的鏈上
所以預處理每個點的深度,然後對每個點向上倍增找到最高的能產生貢獻的點,鏈上都要 \(+1\),做個差分即可。
COde
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int n, m;
int f[N][25], dep[N];
int val[N];
int Find(int x)
{
int d = dep[x];
for(int i = 20; i >= 0; i--)
if(d - dep[f[x][i]] <= m)
x = f[x][i];
return f[x][0];
}
signed main()
{
scanf("%lld%lld", &n, &m);
for(int i = 2; i <= n; i++)
{
int w;
scanf("%lld%lld", &f[i][0], &w);
dep[i] = dep[f[i][0]] + w;
for(int j = 1; j <= 20; j++)
f[i][j] = f[f[i][j - 1]][j - 1];
}
for(int i = n; i >= 1; i--)
{
int x = Find(i);
val[x]--, val[i]++;
}
for(int i = n; i >= 1; i--)
val[f[i][0]] += val[i];
for(int i = 1; i <= n; i++)
printf("%lld\n", val[i]);
return 0;
}
USACO18FEB Snow Boots G
見 noip2021訓練1 最後一題
USACO12FEB Nearby Cows G
非常的套路
分別求出每個點子樹內的答案和子樹外的答案,求和即可。
在求子樹外的時候細節還是比較多
初始化陣列不要 memset()
,那樣複雜度就變成 \(O(n^2)\) 了,需要用多少就初始化多少。
Code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int n, m, c[N];
vector <int> G[N];
int f[N][25], g[N][25], pre[25], suf[N][25];
void dfs1(int u, int fa)
{
for(int i = 0; i <= m; i++)
f[u][i] = c[u];
for(auto v : G[u])
{
if(v == fa) continue;
dfs1(v, u);
for(int i = 1; i <= m; i++)
f[u][i] += f[v][i - 1];
}
return;
}
void dfs2(int u, int fa)
{
vector <int> ch;
for(auto v : G[u])
if(v != fa) ch.pb(v);
memset(pre, 0, sizeof(pre));
for(int i = 0; i <= (int)ch.size(); i++)
for(int j = 0; j <= m; j++)
suf[i][j] = 0;
for(int i = (int)ch.size() - 1; i >= 0; i--)
{
int v = ch[i];
for(int j = 0; j <= m; j++)
suf[i][j] = suf[i + 1][j] + f[v][j];
for(int j = 1; j <= m; j++)
g[v][j] = c[u];
}
for(int i = 0; i < (int)ch.size(); i++)
{
int v = ch[i];
for(int j = 2; j <= m; j++)
g[v][j] += pre[j - 2] + suf[i + 1][j - 2] + g[u][j - 1];
for(int j = 0; j <= m; j++)
pre[j] += f[v][j];
}
for(auto v : ch) dfs2(v, u);
return;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1, u, v; i < n; i++)
{
scanf("%d%d", &u, &v);
G[u].pb(v), G[v].pb(u);
}
for(int i = 1; i <= n; i++)
scanf("%d", &c[i]);
dfs1(1, 0);
dfs2(1, 0);
for(int i = 1; i <= n; i++)
printf("%d\n", f[i][m] + g[i][m]);
return 0;
}
USACO05DEC Layout G
差分約束板子題
\(dis_x-dis_y\le w\qquad dis_x\le dis_y+w\qquad add(y,x,w)\)
\(dis_x-dis_y\ge w\qquad dis_y\le dis_x-w\qquad add(x,y,-w)\)
有負環無解,\(dis_n=inf\) 可以無窮遠
(很久以前的程式碼了)
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 1010
#define M 21010
#define INF 0x3f3f3f3f
using namespace std;
int n,ml,md;
struct edge
{
int v,w,nxt;
}e[M];
int tot,head[N];
void add(int a,int b,int d)
{
e[++tot]=(edge){b,d,head[a]};
head[a]=tot;
}
int dis[N],vis[N],cnt[N];
void spfa(int S)
{
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
for(int i=0; i<=n; i++)
dis[i]=(i==S)?0:INF;
queue<int>que;
vis[S]=1;
que.push(S);
while(!que.empty())
{
int u=que.front();
que.pop();
vis[u]=0;
if((++cnt[u])>=n)
{
printf("-1\n");
exit(0);
}
for(int i=head[u]; i; i=e[i].nxt)
{
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
if(!vis[v])
{
que.push(v);
vis[v]=1;
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&ml,&md);
for(int i=1; i<=ml; i++)
{
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
add(a,b,d);
}
for(int i=1; i<=md; i++)
{
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
add(b,a,-d);
}
for(int i=1; i<=n; i++)
add(0,i,0);
spfa(0);
spfa(1);
if(dis[n]==INF) printf("-2\n");
else printf("%d\n",dis[n]);
return 0;
}
USACO08MAR Land Acquisition G
斜率優化板子題,暑假的時候學長講過,但當時沒寫 qaq
我把 \(x,y\) 分別當作長和寬
首先可以將被包含的矩形去掉,然後剩下的矩形 \(x\) 遞增,\(y\) 遞減
設 \(f_i\) 表示前 \(i\) 個矩形的最小代價
\(f_i=min\{f_{j-1}+x_i\times y_j\}\ (1\le j \le i)\)
現在已經有了 \(O(n^2)\) 的暴力 dp,考慮優化
比較顯然的斜率優化,先將式子變形一下
\(f_{j-1}=-x_i\times y_i+f_i\)
把 \(y_i\) 看作 \(x\),\(f_{j-1}\) 看作 \(y\),\(x_i\) 看作 \(k\),\(f_i\) 看作 \(b\)
然後就得到了一次函式的基本形式 \(y=-k\times x + b\)
\(k\) 是已知的 \(x_i\) 並且遞增,現在就是要最小化截距。
要維護一個下凸包,將已知斜率的一次函式與下凸包相切,就是最小的截距了。
維護下凸包可以用單調佇列維護一個斜率遞增的區間,當隊首的斜率小於 \(x_i\) 時就已經沒用了,因為後面的斜率遞增,不可能再碰到隊首了。
入隊時只需要判斷一下 隊尾與隊尾前一個點的斜率 和 \((y_i,f_{i-1})\) 與隊尾的斜率 哪個大。
這個畫圖模擬一下就很好理解了,大概就是一個在一次象限的下凸包和斜率為負的一函式。
注意斜率是負的。
Code
#include <bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N = 5e4 + 5;
int n;
struct node
{
ll x, y;
friend bool operator < (node a, node b)
{
return a.y == b.y ? a.x > b.x : a.y > b.y;
}
}a[N];
int cnt, l, r, q[N];
db k[N];
ll f[N];
db slope(int i, int j)
{
return (db)(f[j - 1] - f[i - 1]) / (db)(a[i].y - a[j].y);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld%lld", &a[i].x, &a[i].y);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; i++)
if(a[cnt].x < a[i].x) a[++cnt] = a[i];
l = 1, r = 0;
for(int i = 1; i <= cnt; i++)
{
while(l < r && k[r - 1] >= slope(q[r], i)) r--;
k[r] = slope(q[r], i), q[++r] = i;
while(l < r && k[l] <= a[i].x) l++;
f[i] = f[q[l] - 1] + a[i].x * a[q[l]].y;
}
printf("%lld\n", f[cnt]);
return 0;
}
USACO17FEB Why Did the Cow Cross the Road III G
看到這題第一反應就是樹狀陣列,然後試了試,過了樣例,寫完就過了(?
其實就是求每個數兩次出現的位置間有多少個數出現了一次。
用樹狀陣列維護字首和,如果 \(a\) 是第一次出現,直接 \(+1\),否則將答案加上兩次出現位置中間的和。
其實就是中間出現了一次的數的個數。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
int n, a[N], pos[N];
ll c[N], ans;
void add(int x, int y)
{
for(; x <= n; x += x & -x)
c[x] += y;
}
ll qry(int x)
{
ll res = 0;
for(; x; x -= x & -x)
res += c[x];
return res;
}
int main()
{
scanf("%d", &n);
n <<= 1;
for(int i = 1; i <= n; i++)
{
int a;
scanf("%d", &a);
if(!pos[a]) add(i, 1), pos[a] = i;
else add(pos[a], -1), ans += qry(i) - qry(pos[a]);
}
printf("%lld\n", ans);
return 0;
}
USACO13JAN Seating G
比較套路的線段樹題
維護區間最長連續 \(1\) 的個數
分別維護最左邊,最右邊最長的長度,然後合併一下。
在查詢時先找左邊,再找中間(將兩邊合併起來),最後找右邊。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m, ans;
namespace seg
{
#define ls (rt << 1)
#define rs (rt << 1 | 1)
int mx[N << 2], lmx[N << 2], rmx[N << 2];
int tag[N << 2], len[N << 2];
void pushup(int rt)
{
lmx[rt] = lmx[ls];
rmx[rt] = rmx[rs];
if(lmx[rt] == len[ls]) lmx[rt] += lmx[rs];
if(rmx[rt] == len[rs]) rmx[rt] += rmx[ls];
mx[rt] = max(max(mx[ls], mx[rs]), rmx[ls] + lmx[rs]);
}
void build(int l, int r, int rt)
{
len[rt] = r - l + 1;
tag[rt] = -1;
mx[rt] = lmx[rt] = rmx[rt] = len[rt];
if(l == r) return;
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
}
void pushdown(int rt)
{
if(tag[rt] != -1)
{
mx[ls] = lmx[ls] = rmx[ls] = tag[rt] ? len[ls] : 0;
mx[rs] = lmx[rs] = rmx[rs] = tag[rt] ? len[rs] : 0;
tag[ls] = tag[rs] = tag[rt];
tag[rt] = -1;
}
}
void update(int L, int R, int v, int l, int r, int rt)
{
if(l > R || r < L) return;
if(L <= l && r <= R)
{
mx[rt] = lmx[rt] = rmx[rt] = v ? len[rt] : 0;
tag[rt] = v;
return;
}
pushdown(rt);
int mid = (l + r) >> 1;
update(L, R, v, l, mid, ls);
update(L, R, v, mid + 1, r, rs);
pushup(rt);
}
int query(int p, int l, int r, int rt)
{
if(l == r) return l;
pushdown(rt);
int mid = (l + r) >> 1;
if(mx[ls] >= p) return query(p, l, mid, ls);
if(rmx[ls] + lmx[rs] >= p) return mid - rmx[ls] + 1;
return query(p, mid + 1, r, rs);
}
}
int main()
{
scanf("%d%d", &n, &m);
seg :: build(1, n, 1);
while(m--)
{
char op[5];
scanf("%s", op);
if(op[0] == 'A')
{
int p;
scanf("%d", &p);
if(seg :: mx[1] < p)
{
ans++;
continue;
}
int l = seg :: query(p, 1, n, 1);
seg :: update(l, l + p - 1, 0, 1, n, 1);
}
else
{
int a, b;
scanf("%d%d", &a, &b);
seg :: update(a, b, 1, 1, n, 1);
}
}
printf("%d\n", ans);
return 0;
}