ZYC和ZZY太强了
前言
由于A&B组题目相同,所以说一起写,但是貌似……A组第三题不会做
B组部分
JZOJ 1252 洛谷 5194 天平
题目
在 n n n个数中选择若干个,使它们的和不超过 c c c且最大
分析
倒序dfs,并用前缀和优化
代码
#include <cstdio>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n; long long c,a[41],s[41],ans;
inline void dfs(int dep,long long sum){
if (s[dep-1]<=c-sum) ans=max(ans,s[dep-1]+sum);
else{
ans=max(ans,sum);
for (rr int i=dep-1;i;--i)
if (c-sum>=a[i]) dfs(i,sum+a[i]);
}
}
signed main(){
scanf("%d%lld",&n,&c);
for (rr int i=1;i<=n;++i){
scanf("%lld",&a[i]);
if (a[i]>c) i--,n--;
else s[i]=s[i-1]+a[i];
}
dfs(n+1,0);
return !printf("%lld",ans);
}
JZOJ 1274 游历路线
题目
从1号点到 n n n号点,不能停留,在每一个周期每条单向路有一定的费用,问到第 m m m天的最小费用
代码(线性动态规划)
#include <cstdio>
#include <cstring>
#include <cctype>
#define rr register
using namespace std;
int n,m,w[101][101][21],dp[201][101];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline signed min(int a,int b){return (a<b)?a:b;}
signed main(){
freopen("lines.in","r",stdin);
freopen("lines.out","w",stdout);
n=iut(); m=iut();
memset(w,127/3,sizeof(w));
for (rr int i=1;i<=n;++i)
for (rr int j=1;j<=n;++j) if (i!=j){
w[i][j][0]=iut();
for (rr int k=1;k<=w[i][j][0];++k){
w[i][j][k]=iut();
if (!w[i][j][k]) w[i][j][k]=707406378;
}
}
memset(dp,127/3,sizeof(dp)); dp[0][1]=0;
for (rr int i=1;i<=m;++i)
for (rr int j=1;j<=n;++j) if (dp[i-1][j]!=707406378)
for (rr int now=1;now<=n;++now) if (j!=now)
dp[i][now]=min(dp[i][now],dp[i-1][j]+w[j][now][(i-1)%w[j][now][0]+1]);
if (dp[m][n]!=707406378) printf("%d",dp[m][n]); else putchar(48);
return 0;
}
A&B组部分
JZOJ 4224 食物
题目
分析
那么只要美味度满足要求的食物大小总和可以用这些工具装好,那么计算工具的最小费用即为答案,为什么呢,因为食物可以拆开,但是不满足部分背包
所以这道题可以分成两部分
- 找到满足美味度的最小的空间
- 求出工具的最小费用
这些都可以通过多重背包(二进制拆分变为01背包)实现,但是问题是直接维护空间很难完成第二步,于是可以玄学的把它反转,设 f [ j ] f[j] f[j]表示空间为 j j j的最大美味度,那么容易得到 f [ j ] = f [ j − w [ i ] ] + c [ i ] , c [ i ] 表 示 美 味 度 , w [ i ] 表 示 空 间 f[j]=f[j-w[i]]+c[i],c[i]表示美味度,w[i]表示空间 f[j]=f[j−w[i]]+c[i],c[i]表示美味度,w[i]表示空间,然后只要 f [ j ] ≥ p f[j]\geq p f[j]≥p那么对 j j j取最小值,然后取到这个最小值再用一遍多重背包求得答案
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int M=50000; int f[M+105],dp[M+5];
struct rec{int w,c;}a[1201],b[1201];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline void min(int &a,int b){if (a>b) a=b;}
inline void max(int &a,int b){if (a<b) a=b;}
signed main(){
for (rr int t=iut();t;--t){
rr int n1=iut(),n2=iut(),p=iut(),i1,poi=M+1,ans=M+1;
for (i1=n1,n1=0;i1;--i1){//二进制拆分
rr int w=iut(),c=iut(),t=iut();
for (rr int j=1;t>=j;t-=j,j<<=1)
a[++n1]=(rec){w*j,c*j};
if (t) a[++n1]=(rec){w*t,c*t};
}
for (i1=n2,n2=0;i1;--i1){
rr int c=iut(),w=iut(),t=iut();
for (rr int j=1;t>=j;t-=j,j<<=1)
b[++n2]=(rec){w*j,c*j};
if (t) b[++n2]=(rec){w*t,c*t};
}
fill(f+1,f+M+101,100001); f[0]=0;
for (rr int i=1;i<=n1;++i)
for (rr int j=M+100;j>=a[i].w;--j){//可能还会超过一点点
min(f[j],f[j-a[i].w]+a[i].c);
if (j>=p) min(poi,f[j]);
}
fill(dp+1,dp+M+1,-100001); dp[0]=0;
for (rr int i=1;i<=n2;++i)
for (rr int j=ans;j>=b[i].w;--j){
max(dp[j],dp[j-b[i].w]+b[i].c);
if (dp[j]>=poi) min(ans,j);
}
if (ans>M) printf("TAT\n"); else printf("%d\n",ans);
}
return 0;
}
A组部分
JZOJ 4223 旅游
题目
询问有多少个无序点对 ( x , y ) (x,y) (x,y)至少有一条最大边权不超过 d d d的路径
分析
那么这道题适合离线处理,用并查集维护连通块的大小,从小到大排序边权,从小到大排序 d d d,然后对于每一个询问,多出来的就是 ( s o n [ x ] + s o n [ y ] ) × ( s o n [ x ] + s o n [ y ] − 1 ) − ( s o n [ x ] × ( s o n [ x − 1 ] ) + s o n [ y ] × ( s o n [ y ] − 1 ) ) (son[x]+son[y])\times(son[x]+son[y]-1)-(son[x]\times (son[x-1])+son[y]\times (son[y]-1)) (son[x]+son[y])×(son[x]+son[y]−1)−(son[x]×(son[x−1])+son[y]×(son[y]−1))
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
struct node{int x,y,w;}e[100001];
struct rec{int w,rk;}a[5001];
int n,m,q,f[20001],son[20001],ans[5001];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
inline signed getf(int u){return (f[u]==u)?u:f[u]=getf(f[u]);}
bool cmp1(node a,node b){return a.w<b.w;}
bool cmp2(rec x,rec y){return x.w<y.w;}
signed main(){
for (rr int t=iut();t;--t){
n=iut(); m=iut(); q=iut();
for (rr int i=1;i<=m;++i) e[i]=(node){iut(),iut(),iut()};
for (rr int i=1;i<=n;++i) son[f[i]=i]=1;
for (rr int i=1;i<=q;++i) a[i]=(rec){iut(),i};
sort(e+1,e+1+m,cmp1); sort(a+1,a+1+q,cmp2); rr int j=1;
for (rr int i=1;i<=q;++i){
ans[a[i].rk]=ans[a[i-1].rk];
while (j<=m&&e[j].w<=a[i].w){
rr int fa=getf(e[j].x),fb=getf(e[j].y);
if (fa!=fb){
ans[a[i].rk]-=son[fa]*(son[fa]-1)+son[fb]*(son[fb]-1);
if (fa>fb) fa^=fb,fb^=fa,fa^=fb;
son[f[fa]=fb]+=son[fa];
ans[a[i].rk]+=son[fb]*(son[fb]-1);
}
++j;
}
}
for (rr int i=1;i<=q;++i) print(ans[i]),putchar(10);
}
return 0;
}
JZOJ 4225 宝藏
题目(简化版)
询问树上一个点到另外一个点的期望长度
JZOJ 5814是这道题的弱化版
分析
终于改A了,感谢纪中YYT和SSL_WHF的帮助
首先呢,两点之间距离需要用LCA实现,所以说首先要求一次LCA,然后再想期望步数,应该是要用树形dp的了
设
d
p
1
[
x
]
dp1[x]
dp1[x]表示从
x
x
x到根节点的期望步数,
d
p
2
[
x
]
dp2[x]
dp2[x]表示从根节点到
x
x
x的期望步数,但是有点难,那首先从父节点开始,按照YYT的方法,然后用前缀和求得两个数组,然后最后对于两个点
x
,
y
x,y
x,y,答案就是
d
p
1
[
x
]
−
d
p
1
[
l
c
a
]
+
d
p
2
[
y
]
−
d
p
2
[
l
c
a
]
dp1[x]-dp1[lca]+dp2[y]-dp2[lca]
dp1[x]−dp1[lca]+dp2[y]−dp2[lca]。
首先
d
p
1
′
[
x
]
dp1'[x]
dp1′[x]表示从
x
x
x到父节点的期望步数,
d
p
2
′
[
x
]
dp2'[x]
dp2′[x]表示父节点到
x
x
x 的期望步数
d
p
1
′
[
x
]
=
1
d
e
g
[
x
]
(
度
数
)
+
(
∑
i
∈
s
o
n
x
1
+
d
p
1
′
[
i
]
+
d
p
1
′
[
x
]
(
到
儿
子
再
到
自
己
再
到
父
节
点
)
)
÷
d
e
g
[
x
]
dp1'[x]=\frac{1}{deg[x](度数)}+(\sum_{i\in son_x}1+dp1'[i]+dp1'[x](到儿子再到自己再到父节点))\div deg[x]
dp1′[x]=deg[x](度数)1+(i∈sonx∑1+dp1′[i]+dp1′[x](到儿子再到自己再到父节点))÷deg[x]
尝试化简这个式子得到
d
p
1
′
[
x
]
=
1
d
e
g
[
x
]
+
d
e
g
[
x
]
−
1
(
子
节
点
个
数
)
d
e
g
[
x
]
+
∑
i
∈
s
o
n
x
d
p
1
′
[
i
]
÷
d
e
g
[
x
]
+
(
d
e
g
[
x
]
−
1
)
d
p
1
′
[
x
]
d
e
g
[
x
]
dp1'[x]=\frac{1}{deg[x]}+\frac{deg[x]-1(子节点个数)}{deg[x]}+\sum_{i\in son_x}dp1'[i]\div deg[x]+\frac{(deg[x]-1)dp1'[x]}{deg[x]}
dp1′[x]=deg[x]1+deg[x]deg[x]−1(子节点个数)+i∈sonx∑dp1′[i]÷deg[x]+deg[x](deg[x]−1)dp1′[x]
把最后一项移项得到
d
p
1
′
[
x
]
d
e
g
[
x
]
=
1
+
∑
i
∈
s
o
n
x
d
p
1
′
[
i
]
÷
d
e
g
[
x
]
\frac{dp1'[x]}{deg[x]}=1+\sum_{i\in son_x}dp1'[i]\div deg[x]
deg[x]dp1′[x]=1+i∈sonx∑dp1′[i]÷deg[x]
方程两边同乘
d
e
g
[
x
]
deg[x]
deg[x]得到最后结果
d
p
1
′
[
x
]
=
d
e
g
[
x
]
+
∑
i
∈
s
o
n
x
d
p
1
′
[
i
]
dp1'[x]=deg[x]+\sum_{i\in son_x}dp1'[i]
dp1′[x]=deg[x]+i∈sonx∑dp1′[i]
同样,
d
p
2
′
[
x
]
=
1
d
e
g
[
f
a
]
+
(
∑
i
∈
x
的
兄
弟
1
+
d
p
1
′
[
i
]
+
d
p
2
′
[
x
]
(
到
父
亲
再
到
兄
弟
再
回
去
再
回
到
x
)
)
÷
d
e
g
[
f
a
]
+
1
+
d
p
2
′
[
f
a
]
到
父
亲
再
回
来
d
e
g
[
f
a
]
dp2'[x]=\frac{1}{deg[fa]}+(\sum_{i\in x的兄弟}1+dp1'[i]+dp2'[x](到父亲再到兄弟再回去再回到x))\div {deg[fa]}+\frac{1+dp2'[fa]到父亲再回来}{deg[fa]}
dp2′[x]=deg[fa]1+(i∈x的兄弟∑1+dp1′[i]+dp2′[x](到父亲再到兄弟再回去再回到x))÷deg[fa]+deg[fa]1+dp2′[fa]到父亲再回来
化简方法差不多,那么就可以得到
d
p
2
′
[
x
]
=
d
e
g
[
f
a
]
+
∑
i
∈
x
的
兄
弟
d
p
1
′
[
i
]
+
d
p
2
′
[
f
a
]
dp2'[x]=deg[fa]+\sum_{i\in x的兄弟}dp1'[i]+dp2'[fa]
dp2′[x]=deg[fa]+i∈x的兄弟∑dp1′[i]+dp2′[fa]
但是问题是中间这一坨很难处理,没关系,这个东西就是
d
p
2
′
[
x
]
=
d
e
g
[
f
a
]
+
d
p
1
′
[
f
a
]
−
d
p
1
′
[
x
]
+
d
p
2
′
[
f
a
]
dp2'[x]=deg[fa]+dp1'[fa]-dp1'[x]+dp2'[fa]
dp2′[x]=deg[fa]+dp1′[fa]−dp1′[x]+dp2′[fa]
被带偏了吧,没错,我那时被带偏了
观察
d
p
1
′
[
f
a
]
=
d
e
g
[
f
a
]
+
∑
i
∈
s
o
n
x
d
p
1
′
[
i
]
dp1'[fa]=deg[fa]+\sum_{i\in son_x}dp1'[i]
dp1′[fa]=deg[fa]+∑i∈sonxdp1′[i]
它多出来了一个
d
e
g
[
f
a
]
deg[fa]
deg[fa],那就正好了,那么
d
p
2
′
[
x
]
=
d
p
1
′
[
f
a
]
−
d
p
1
′
[
x
]
+
d
p
2
′
[
f
a
]
dp2'[x]=dp1'[fa]-dp1'[x]+dp2'[fa]
dp2′[x]=dp1′[fa]−dp1′[x]+dp2′[fa]
证明完这些,其实已经比较容易打出来了
代码
#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int N=50010;
struct node{int y,next;}e[N<<1];
int ls[N],dep[N],dp1[N],dp2[N],deg[N],f[N][16],n,k;
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
inline void add(int x,int y){
e[++k]=(node){y,ls[x]}; ls[x]=k; ++deg[x];
e[++k]=(node){x,ls[y]}; ls[y]=k; ++deg[y];
}
inline void dfs1(int x,int fa){//dp1
dep[x]=dep[fa]+1; dp1[x]=deg[x];
for (rr int i=ls[x];i;i=e[i].next)
if (e[i].y!=fa){
f[e[i].y][0]=x;
dfs1(e[i].y,x);
dp1[x]+=dp1[e[i].y];
}
}
inline void dfs2(int x,int fa){//dp2
for (rr int i=ls[x];i;i=e[i].next)
if (e[i].y!=fa){
dp2[e[i].y]=dp1[x]-dp1[e[i].y]+dp2[x];
dfs2(e[i].y,x);
}
}
inline void dfs3(int x,int fa){//处理前缀和
for (rr int i=ls[x];i;i=e[i].next) if (e[i].y!=fa)
dp1[e[i].y]+=dp1[x],dp2[e[i].y]+=dp2[x],dfs3(e[i].y,x);
}
inline signed lca(int x,int y){//求两点间的lca
if (dep[x]<dep[y]) x^=y,y^=x,x^=y;
for (rr int i=15;i>=0;--i) if (dep[x]-(1<<i)>=dep[y]) x=f[x][i];
for (rr int i=15;i>=0;--i) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
if (x==y) return x; else return f[x][0];
}
signed main(){
for (rr int t=iut();t;--t){
memset(deg,0,sizeof(deg));
memset(ls,0,sizeof(ls));
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
n=iut(); k=1; f[1][0]=0;
for (rr int i=1;i<n;++i) add(iut()+1,iut()+1);
dfs1(1,0); dfs2(1,0); dp1[1]=0;dp2[1]=0; dfs3(1,0);
for (rr int j=1;j<16;++j)//树上倍增
for (rr int i=1;i<=n;++i)
f[i][j]=f[f[i][j-1]][j-1];
for (rr int q=iut();q;--q){
rr int len=iut()+1,ls=iut()+1,ans=0;
while (--len){
rr int x=iut()+1,t=lca(x,ls);
ans+=dp1[ls]-dp1[t]+dp2[x]-dp2[t];
ls=x;
}
print(ans); printf(".0000\n"); //必然是一个整数
}
if (t>1) putchar(10);
}
return 0;
}
后续
我明明菜, d a l a o dalao dalao还不承认