好吧,还是决定写题解了,今天开局搞05,然后自闭一小时,此时我们队还是爆零状态,赶紧把06切了,然后放手搞02,和09,双重自闭…然后赶紧去秒了07,搞着搞着突然想玩一波玄学,09费用流建图只加部分的边,然后就过了…接下来搞04,想明白后就是个二分+线段树优化dp,cf上老套路了,一发切了,最后终于是取得了湖南省内第一,庆祝一下下
1002 Blow up the city
解法:我们先倒着建图,加一个虚点,虚点连接所有入度为0(倒着的)的点,然后建立支配树,每次查询 u v,答案就是dep[u] + dep[v] - dep[lca] - 1,为啥要减1,是因为虚点也算进去了。
#include
#define ll long long
#define pb push_back
using namespace std;
const int maxn = 1e5 + 10;
vector<int> pre[maxn], dom[maxn], G[maxn];
int n, m, semi[maxn], idom[maxn], tot;
int id[maxn], dfn[maxn], fa[maxn], father[maxn], val[maxn];
void dfs(int x) {
dfn[x] = ++tot;
id[tot] = x;
for (auto v : G[x]) {
pre[v].pb(x);
if (!dfn[v]) {
fa[v] = x;
dfs(v);
}
}
}
int get(int x) {
if (father[x] == x)
return x;
int y = get(father[x]);
if (dfn[semi[val[father[x]]]] < dfn[semi[val[x]]])
val[x] = val[father[x]];
return father[x] = y;
}
int smin(int x,int y) {return dfn[x] < dfn[y] ? x : y; }
void solve() {
for (int i = tot; i >= 2; i--) {
int u = id[i];
for (auto v : pre[u])
if (dfn[v] < dfn[u])
semi[u] = smin(semi[u], v);
else {
get(v);
semi[u] = smin(semi[u], semi[val[v]]);
}
father[u] = fa[u];
dom[semi[u]].pb(u);
for (auto v : dom[fa[u]]) {
get(v);
int x = val[v];
idom[v] = (dfn[semi[x]] < dfn[semi[v]]) ? x : fa[u];
}
}
for (int i = 2; i <= tot; i++) {
int x = id[i];
if (idom[x] != semi[x])
idom[x] = idom[idom[x]];
}
}
int vis[maxn], f[maxn][20], dep[maxn];
void build() {
for (int i = 1; i <= n; i++)
G[i].clear();
for (int i = 1; i <= n; i++)
if (idom[i])
G[idom[i]].pb(i);
}
void dfs2(int u, int Fa) {
dep[u] = dep[Fa] + 1;
f[u][0] = Fa;
for (int i = 1; i < 20; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (auto v : G[u])
dfs2(v, u);
}
int LCA(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = 19; i >= 0; i--)
if (dep[f[x][i]] >= dep[y])
x = f[x][i];
if (x == y)
return x;
for (int i = 19; i >= 0; i--)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int main() {
int u, v, T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
tot = 0;
for (int i = 1; i <= n + 1; i++) {
pre[i].clear(), dom[i].clear(), G[i].clear();
dfn[i] = idom[i] = vis[i] = 0;
father[i] = val[i] = semi[i] = i;
}
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u, &v);
G[v].pb(u);
vis[u] = 1;
}
n++;
for (int i = 1; i < n; i++)
if (!vis[i])
G[n].pb(i);
dfs(n);
solve();
build();
dfs2(n, 0);
int q;
scanf("%d", &q);
while (q--) {
scanf("%d%d", &u, &v);
int lca = LCA(u, v);
printf("%d\n", dep[u] + dep[v] - dep[lca] - 1);
}
}
}
1004 Distribution of books
题意:要求把一个序列分成连续的k块(可以去掉后缀),使得权值和最大的那一块权值最小,求出这个最小值
解法:二分这个权值,然后根据权值去把序列切块,如果能切成k块甚至更多,那么一定合法,否则不合法,怎么check呢?设d[i]为前 i 个数在最大权值限制在T时最多能切的块数,转移:对于所有的 L < i,如果sum[i] - sum[L] <= T,那么d[i] = max(d[i], d[L] + 1),很显然这个dp check复杂度是n^2,我们变形:sum[i] - T <= sum[L],我们二分找到最小的sum[L],然后在线段树中查其区间[sum[L], max(sum[x])]最大值mx,d[i] = mx + 1即可,然后在线段树的 sum[i]点更新d[i]。
#include
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
int mx[maxn * 4], a[maxn], sz, n, p[maxn], inf = 1e9;
ll sum[maxn], b[maxn];
#define ls o * 2
#define rs o * 2 + 1
#define m (l + r) / 2
int qu(int o, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr)
return mx[o];
int res = 0;
if (ql <= m)
res = max(res, qu(ls, l, m, ql, qr));
if (qr > m)
res = max(res, qu(rs, m + 1, r, ql, qr));
return res;
}
void up(int o, int l, int r, int k, int v) {
if (l == r) {
mx[o] = v;
return;
}
if (k <= m)
up(ls, l, m, k, v);
else
up(rs, m + 1, r, k, v);
mx[o] = max(mx[ls], mx[rs]);
}
int ok(ll T, int k) {
for (int i = 1; i <= sz * 4; i++)
mx[i] = 0;
for (int i = 1; i <= n; i++) {
int cur = lower_bound(b + 1, b + 1 + sz, sum[i] - T) - b;
int v = qu(1, 1, sz, cur, sz);
if (v == k - 1) {
if (v || (!v && sum[i] <= T))
return 1;
}
if (v || (!v && sum[i] <= T))
up(1, 1, sz, p[i], v + 1);
}
return 0;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int mn = 1e9, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
mn = min(mn, a[i]);
sum[i] = sum[i - 1] + a[i];
b[i] = sum[i];
}
sort(b + 1, b + 1 + n);
sz = unique(b + 1, b + 1 + n) - b - 1;
for (int i = 1; i <= n; i++)
p[i] = lower_bound(b + 1, b + 1 + sz, sum[i]) -b;
ll N = abs(1ll * mn * n);
ll L = 0, R = N + 1e9;
while (L < R) {
ll mid = (L + R) / 2;
if (!ok(mid - N, k))
L = mid + 1;
else
R = mid;
}
printf("%lld\n", L - N);
}
}
1005 Easy Math Problem
待补
1006 Fansblog
快速幂套快速乘。签到
#include
#define ll long long
using namespace std;
const int maxn = 1e7 + 10000;
int vis[maxn], pri[maxn / 10], cnt;
ll mod;
void init(int n) {
int ans = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i])
pri[++cnt] = i;
for (int j = 1; j <= cnt && pri[j] * i <= n; j++) {
vis[pri[j] * i] = 1;
if (i % pri[j] == 0)
break;
}
}
}
int ok(ll n) {
for (int i = 1; 1ll * pri[i] * pri[i] <= n; i++)
if (n % pri[i] == 0)
return 0;
return 1;
}
void add(ll &x, ll y) {
x += y;
if (x >= mod)
x -= mod;
}
ll ksc(ll &x, ll y) {
ll res = 0;
while (y) {
if (y & 1)
add(res, x);
add(x, x);
y /= 2;
}
x = res;
}
ll ksm(ll x, ll y) {
ll res = 1;
while (y) {
if (y & 1)
ksc(res, x);
ksc(x, x);
y /= 2;
}
return res;
}
int main() {
int T;
init(1e7 + 1000);
cin>>T;
while (T--) {
ll n;
cin>>mod;
for (n = mod - 1; n; n--)
if (ok(n))
break;
//printf("n = %lld\n", n);
//continue;
ll ans = 1;
for (ll i = mod - 2; i > n; i--) {
ll inv = ksm(i, mod - 2);
ksc(ans, inv);
}
cout<<ans<<endl;
}
}
1007 Find the answer
cf原题,线段树水题
#include
#define ll long long
using namespace std;
const int maxn = 2e5 + 10;
ll sum[maxn * 4];
int a[maxn], b[maxn], val[maxn * 4];
#define ls o * 2
#define rs o * 2 + 1
#define mid (l + r) / 2
void up(int o, int l, int r, int k) {
if (l == r) {
sum[o] += b[l];
val[o]++;
return;
}
if (k <= mid)
up(ls, l, mid, k);
else
up(rs, mid + 1, r, k);
sum[o] = sum[ls] + sum[rs];
val[o] = val[ls] + val[rs];
}
int qu(int o, int l, int r, ll v) {
if (l == r) {
if (!v || !b[l])
return 0;
return (v + (b[l] - 1)) / b[l];
}
if (sum[rs] < v)
return qu(ls, l, mid, v - sum[rs]) + val[rs];
return qu(rs, mid + 1, r, v);
}
int main()
{
int Cas;
scanf("%d", &Cas);
while (Cas--) {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d" ,&a[i]);
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
int sz = unique(b + 1, b + 1 + n) - b - 1;
for (int i = 1; i <= sz * 4; i++)
sum[i] = val[i] = 0;
ll res = 0;
for (int i = 1; i <= n; i++) {
res += a[i];
int ans = 0;
if (res > m)
ans = qu(1, 1, sz, res - m);
int k = lower_bound(b + 1, b + 1 + sz, a[i]) - b;
up(1, 1, sz, k);
printf("%d ", ans);
}
puts("");
}
}
1008 Game
题意:有两种操作: 1 l r,查询这个区间有多少子区间异或和为0,2 pos,将a[pos]的值与a[pos + 1]互换
解法:很明显的带修改莫队,我们维护异或前缀和,每次移动端点看该端点前缀异或和的值出现了几次即可。
#include
#define ll long long
using namespace std;
const int maxn = 1e5 + 10, N = 1024 * 1025;
int cnt[N], a[maxn], b[maxn], block;
ll ans[maxn];
struct node {
int id, l, r, t;
bool operator<(const node &tmp) const {
if (l / block != tmp.l / block)
return l < tmp.l;
else if (r / block != tmp.r / block)
return r < tmp.r;
return t < tmp.t;
}
} q[maxn];
struct node2 {
int pos, t, pre, val;
} d[maxn];
int gao(int x, int opt) {
cnt[x] += opt;
if (opt == 1)
return cnt[x] - 1;
return cnt[x];
}
int main() {
int n, m, opt, l, r;
while (~scanf("%d%d", &n, &m)) {
for (int i = 0; i <= 1024 * 1024; i++)
cnt[i] = 0;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), b[i] = b[i - 1] ^ a[i];
int time = 0, ID = 0;
for (int i = 1; i <= m; i++) {
scanf("%d", &opt);
if (opt == 1) {
scanf("%d%d", &l, &r);
++ID; --l;
q[ID] = node{ID, l, r, time};
}
else {
scanf("%d", &l);
int pre = b[l];
int val = b[l] ^ a[l] ^ a[l + 1];
++time;
d[time] = node2{l, time, pre, val};
swap(a[l], a[l + 1]);
b[l] = val;
}
}
block = pow(n, 2.0 / 3) + 1;
sort(q + 1, q + 1 + ID);
l = 1, r = 0;
ll sum = 0;
for (int i = 1; i <= ID; i++) {
while (l < q[i].l)
sum -= gao(b[l++], -1);
while (l > q[i].l)
sum += gao(b[--l], 1);
while (r < q[i].r)
sum += gao(b[++r], 1);
while (r > q[i].r)
sum -= gao(b[r--], -1);
while (time > q[i].t) {
if (d[time].pos >= l && d[time].pos <= r) {
sum -= gao(d[time].val, -1);
sum += gao(d[time].pre, 1);
}
b[d[time].pos] = d[time].pre;
time--;
}
while (time < q[i].t) {
++time;
if (d[time].pos >= l && d[time].pos <= r) {
sum -= gao(d[time].pre, -1);
sum += gao(d[time].val, 1);
}
b[d[time].pos] = d[time].val;
}
ans[q[i].id] = 1ll * (r - l + 1) * (r - l) / 2 - sum;
}
for (int i = 1; i <= ID; i++)
printf("%lld\n", ans[i]);
}
}
1009 K Subsequence
题意:对于一个长度为n的序列,选取k个子序列(每个点只能被选一次),求k个子序列的总和最大。
解法:玄学过的这题,对于每个点拆为入点和出点,连接入点到出点费用为负的点权,流量为1,源点SS连接所有点入点,所有点出点连接汇点T,流量为1,费用为0,对于每个点ai,往后面找最多100个权值大于等于ai的 j 点(别问为啥100个,玄学),i 的出点连接 j 的入点,最后超级源S连接SS,流量为k,费用为0,然后跑一遍费用流即可
#include
using namespace std;
const int maxn=4020,inf=1e9;
struct Edge
{
int from,to,cap,flow,cost;
Edge(int a,int b,int c,int d,int e)
{
from=a,to=b,cap=c,flow=d,cost=e;
}
};
struct MCMF{
int n,m,s,t;
vector<Edge>edges;
vector<int>G[maxn];
int inq[maxn];
int d[maxn];
int p[maxn];
int a[maxn];
void init(int n)
{
this->n=n;
for(int i=0;i<=n;i++)G[i].clear();
edges.clear();
}
void add(int from,int to,int cap,int cost)
{
edges.push_back(Edge(from,to,cap,0,cost));
edges.push_back(Edge(to,from,0,0,-cost));
m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool bellman(int s,int t,int& flow,int& cost)
{
for(int i=0;i<=n;i++)d[i]=inf,inq[i]=0;
d[s]=0;inq[s]=1;p[s]=0;a[s]=inf;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();Q.pop();
inq[u]=0;
for(int i=0;i<G[u].size();i++)
{
Edge& e=edges[G[u][i]];
if(e.cap>e.flow&&d[e.to]>d[u]+e.cost)
{
d[e.to]=d[u]+e.cost;
p[e.to]=G[u][i];
a[e.to]=min(a[u],e.cap-e.flow);
if(!inq[e.to])
{
Q.push(e.to);
inq[e.to]=1;
}
}
}
}
if(d[t]==inf)return false;
flow+=a[t];
cost+=d[t]*a[t];
int u=t;
while(u!=s)
{
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
u=edges[p[u]].from;
}
return true;
}
int mincost(int s,int t)
{
int flow=0,cost=0;
while(bellman(s,t,flow,cost));
return cost;
}
}solve;
int a[maxn], q[maxn];
int main()
{
int Cas;
scanf("%d", &Cas);
while (Cas--) {
int n, k;
scanf("%d%d", &n, &k);
int S = 0, T = n * 2 + 1, SS = T + 1;
solve.init(SS + 1);
solve.add(S, SS, k, 0);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
solve.add(i, i + n, 1, -a[i]);
solve.add(i + n, T, 1, 0);
solve.add(SS, i, 1, 0);
}
for (int i = 1; i < n; i++)
for (int j = i + 1, ok = 0; j <= n && ok <= 100; j++)
if (a[j] >= a[i])
ok++, solve.add(i + n, j, 1, 0);
printf("%d\n", -solve.mincost(S, T));
}
}
dij版费用流,可以建满图:
#include
using namespace std;
#define PII pair
#define fr first
#define sc second
#define mp make_pair
#define INF 0x3f3f3f3f
const int N = 4010;
int n,k;
int a[N];
struct MinCostFlow{
struct edge{
int v,f,c,r;
edge(int _v, int _f, int _c, int _r) :v(_v), f(_f), c(_c), r(_r) {}
};
int V=0,h[N],dis[N],pv[N],pe[N];
vector <edge> G[N];
void init(int n){
for(int i=0;i<=V;i++) G[i].clear();
V=n;
}
void add(int u,int v,int f,int c){
G[u].push_back(edge(v,f,c,G[v].size() ));
G[v].push_back(edge(u,0,-c,G[u].size()-1 ));
}
PII MCMF(int s,int t,int Flow){
int cost=0,flow=0,newflow;fill(h,h+1+V,0);
while(Flow){
priority_queue <PII,vector<PII>,greater<PII> > Q;
fill(dis,dis+V+1,INF);
dis[s]=0,Q.push(mp(0,s));
while(!Q.empty()){
PII now=Q.top();Q.pop();
int u=now.sc;
if(dis[u]<now.fr) continue;
for(int i=0;i<G[u].size();i++){
edge &e=G[u][i];
if(e.f>0&&dis[e.v]>dis[u]+e.c+h[u]-h[e.v]){
dis[e.v]=dis[u]+e.c+h[u]-h[e.v];
pv[e.v]=u,pe[e.v]=i;
Q.push(PII(dis[e.v],e.v));
}
}
}
if(dis[t]==INF) break;
for(int i=0;i<=V;i++) h[i]+=dis[i];
newflow=Flow;
for(int x=t;x!=s;x=pv[x]) newflow=min(newflow,G[pv[x]][pe[x]].f);
Flow-=newflow,flow+=newflow,cost+=newflow*h[t];
for(int x=t;x!=s;x=pv[x]){
edge &e=G[pv[x]][pe[x]];
e.f-=newflow;
G[x][e.r].f+=newflow;
}
}
return mp(flow,cost);
}
}A;
int cas;
int main(){
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &k);
int S = 0, T = 2 * n + 1, SS = T + 1;
A.init(SS+1);
A.add(S, SS, k, 0);
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
A.add(i, n+i, 1, -a[i]);
A.add(i + n, T, 1, 0);
A.add(SS, i, 1, 0);
}
for(int i = 1; i < n; i++)
for(int j = i + 1; j <= n; j++)
if(a[j] >= a[i])
A.add(i + n, j, 1, 0);
PII Ans = A.MCMF(S, T, INF);
printf("%d\n", -Ans.sc);
}
return 0;
}
1011 Squrirrel
最好的官方解法:可以先考虑不删边版本,题意化简为求每个点到最远叶子距离,一次dfs找到每个点向 其子树方向走到叶子的最远距离 和次远距离 ,第二次dfs更新向父亲方向走的最远路径 ,对 于每个点的答案就是 。在考虑允许一次删边,就在DP多开一维表删边与否,并记录子 树方向到叶子的最大,次大,第三大距离和向父亲方向走的最远距离。需要注意的是更新 的时候需 要判断该节点是否是其父亲节点往叶子方向走最远,次远路径上的点,分情况转移即可
#include
#define pi pair
#define mk make_pair
using namespace std;
const int maxn = 2e5 + 10;
vector<pi> G[maxn];
int fi[maxn][2], se[maxn][2], th[maxn][2], fa[maxn][2];
int f[maxn], s[maxn], t[maxn], dis[maxn], ans[maxn];
void dfs(int u, int Fa) {
for (auto tmp : G[u]) {
if (tmp.first == Fa)
continue;
int v = tmp.first;
int w = tmp.second;
dis[v] = w;
dfs(v, u);
if (fi[u][0] < fi[v][0] + w) {
th[u][0] = se[u][0];
s[u] = f[u];
se[u][0] = fi[u][0];
f[u] = v;
fi[u][0] = fi[v][0] + w;
}
else if (se[u][0] < fi[v][0] + w) {
th[u][0] = se[u][0];
s[u] = v;
se[u][0] = fi[v][0] + w;
}
else if (th[u][0] < fi[v][0] + w)
th[u][0] = fi[v][0] + w;
}
fi[u][1] = min(fi[f[u]][0], dis[f[u]] + max(fi[f[u]][1], se[f[u]][0]));
se[u][1] = min(fi[s[u]][0], dis[s[u]] + max(fi[s[u]][1], se[s[u]][0]));
}
void dfs2(int u, int Fa) {
ans[u] = min(max(max(fi[u][1], se[u][0]), fa[u][0]), max(fi[u][0], fa[u][1]));
for (auto tmp : G[u]) {
if (tmp.first == Fa)
continue;
int v = tmp.first;
int w = tmp.second;
if (v == f[u]) {
fa[v][0] = max(fa[u][0], se[u][0]) + w;
fa[v][1] = min(max(fa[u][0], se[u][0]), min(max(max(se[u][1], th[u][0]), fa[u][0]), max(fa[u][1], se[u][0])) + w);
}
else if (v == s[u]) {
fa[v][0] = max(fa[u][0], fi[u][0]) + w;
fa[v][1] = min(max(fa[u][0], fi[u][0]), min(max(max(fi[u][1], th[u][0]), fa[u][0]), max(fa[u][1], fi[u][0])) + w);
}
else {
fa[v][0] = max(fa[u][0], fi[u][0]) + w;
fa[v][1] = min(max(fa[u][0], fi[u][0]), min(max(max(fi[u][1], se[u][0]), fa[u][0]), max(fa[u][1], fi[u][0])) + w);
}
dfs2(v, u);
}
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n, u, v, w;
scanf("%d", &n);
for (int i = 0; i <= n; i++) {
G[i].clear();
f[i] = s[i] = 0;
for (int j = 0; j < 2; j++)
fi[i][j] = se[i][j] = th[i][j] = fa[i][j] = 0;
}
for (int i = 1; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(mk(v, w));
G[v].push_back(mk(u, w));
}
dfs(1, 0);
dfs2(1, 0);
int mn = 1e9, x;
for (int i = 1; i <= n; i++)
if (ans[i] < mn)
mn = ans[i], x = i;
printf("%d %d\n", x, mn);
}
}