2023年
11.29
1. P9027 [CCC2021 S5] Math Homework
构造题:先算出每个位置拥有的所有因子的最小公倍数,然后维护每一段区间的 ,由于 具有结合性,所以可以使用 表来维护。最后再查询每一段的 是否符合原题给的
2. P9402 [POI2020-2021R3] Droga do domu
图论题:分层图
中间实现过程很麻烦,还要优化空间,真难调
3. P4306 [JSOI2010] 连通数
图论题:建反图 缩点 在反图上跑拓扑。用 来存储每个点之间是否可达,转移是否可达时写 ,用起来更方便。最后统计答案时,若二者可达,则对答案的贡献时
4. P6268 [SHOI2002] 舞会
图论题:二分图的最大匹配。 答案为 最大匹配的边数
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int match[N],vis[N],n,m,col[N],g[N][N];
void get_color(int u,int c){
col[u]=c;
for(int i=1;i<=n;i++){
if(!col[i]&&g[u][i])
get_color(i,3-c);
}
}
int find(int u){
for(int i=1;i<=n;i++){
if(!vis[i]&&g[u][i]){
vis[i]=1;
if(match[i]==-1||find(match[i])){
match[i]=u;
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
u++; v++;
g[u][v]=1; g[v][u]=1;
}
for(int i=1;i<=n;i++)
if(!col[i]) get_color(i,1);
int res=0;
memset(match,-1,sizeof(match));
for(int i=1;i<=n;i++){
if(col[i]!=1) continue;
// printf("%d\n",i);
memset(vis,0,sizeof(vis));
res+=find(i);
}
// printf("%d\n",res);
printf("%d\n",n-res);
return 0;
}
5. P4329 [COCI2006-2007#1] Bond
状压 :每一次状态统计出有几个 ,即有几个人,为 ,此时对于第 个人来说,他可以做的任务为那些位置为 的任务,并且这个第 个人也对应着我们读入时的第 这个人。仔细想想,会发现这样转移是把所有情况都涵盖进去了。
#include<bits/stdc++.h>
using namespace std;
const int N=22;
int n;
double a[N][N],f[1<<N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lf",&a[i][j]),a[i][j]*=0.01;
f[0]=1;
for(int i=0;i<(1<<n);i++){
int cnt=0;
for(int j=1;j<=n;j++)
if(i&(1<<(j-1))) cnt++;
for(int j=1;j<=n;j++)
if(i&(1<<(j-1)))
f[i]=max(f[i],f[i^(1<<(j-1))]*a[cnt][j]);
}
printf("%.6lf",f[(1<<n)-1]*100);
return 0;
}
6. P4376 [USACO18OPEN] Milking Order G
图论题:二分 暴力建图 跑拓扑。每一遍拓扑,如果发现跑完拓扑之后仍然有点的 ,说明这个图存在环,不符合条件,接着二分。
在有向图中,拓扑可以用来判断是否存在环。
7. P3961 [TJOI2013] 黄金矿工
动态规划:有依赖的背包问题,可以作为一个模板。
#include<bits/stdc++.h>
using namespace std;
const int N=210,TT=4e4+10;
struct node{
int x,y,t,v;
double k;
}a[N];
int n,f[TT],num[N],v[N][N],t[N][N],cnt,T;
bool cmp(node t1,node t2){
if(t1.k==t2.k) return t1.y<t2.y;
return t1.k<t2.k;
}
int main(){
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++){
int x,y,t,v;
scanf("%d%d%d%d",&x,&y,&t,&v);
double k=x*1.0/y;
a[i]={x,y,t,v,k};
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
if(i==1||a[i].k!=a[i-1].k)
cnt++;
if(!num[cnt]){
num[cnt]++;
v[cnt][num[cnt]]=a[i].v;
t[cnt][num[cnt]]=a[i].t;
}
else{
num[cnt]++;
v[cnt][num[cnt]]=v[cnt][num[cnt]-1]+a[i].v;
t[cnt][num[cnt]]=t[cnt][num[cnt]-1]+a[i].t;
}
}
for(int i=1;i<=cnt;i++)
for(int j=T;j>=t[i][1];j--){
int maxn=f[j];
for(int k=1;k<=num[i];k++)
if(j>=t[i][k])
maxn=max(maxn,f[j-t[i][k]]+v[i][k]);
f[j]=maxn;
}
printf("%d\n",f[T]);
return 0;
}
8. P2227 [HNOI2001] 洗牌机
找规律题。 我们多模拟几次,发现会有一个循环节,所以先找到循环节大小,之后总次数减去多个循环节后,再递推几次就ok了
9. P8744 [蓝桥杯 2021 省 A] 左孩子右兄弟
树形 :很简单
10. P3177 [HAOI2015] 树上染色
树形 :设计状态: 表示以 为根的子树中有 个黑点的收益最大值
11. P4362 [NOI2002] 贪吃的九头龙
树形 : 表示以 为根的子树中有 个果子被最大的龙头吃掉,并且当前 这个点是否被吃掉的最小的“难受值”。
12. P1270 “访问”美术馆
树形 : 表示以 为根的子树,花费了 秒,所可以得到的最大的画的数量
13. P4438 [HNOI/AHOI2018] 道路
树形 : 表示当前点 到根节点之间的路径中,有 条公路没有翻新,有 条铁路没有翻新的最小的不便利值。这道题使用倒推,进行 ,之后输出为 。
优化空间的技巧(很重要):
但这道题空间如果正常开的话会很大,会开 ,所以会炸空间,要优化。
因为这道题是一个完全二叉树,所以每个父节点 只会被其两个子节点 更新,所以我们在用 更新完其父节点 后就可以扔了,所以我们可以使用类似时间戳的东西来给每个点重新赋一个编号。当每一个父节点被更新完后,可以直接 意思是将其两个儿子直接扔了。
具体看代码:
int dfn[N],tim;
void dfs(int u,int x,int y){
if(!s[u]){
dfn[u]=++tim;
for(int i=0;i<=x;i++)
for(int j=0;j<=y;j++)
f[dfn[u]][i][j]=c[u]*(a[u]+i)*(b[u]+j);
return;
}
dfn[u]=++tim;
dfs(s[u],x+1,y); dfs(t[u],x,y+1);
for(int i=0;i<=x;i++)
for(int j=0;j<=y;j++){
int rt=dfn[u],ls=dfn[s[u]],rs=dfn[t[u]];
f[rt][i][j]=min(f[ls][i+1][j]+f[rs][i][j],f[ls][i][j]+f[rs][i][j+1]);
}
tim-=2;
}
14. P7537 [COCI2016-2017#4] Rima
15. P4471 [BJWC2018] 词韵
14、15二者题目一样
16. P3174 [HAOI2009] 毛毛虫
14、15、16同一类型的树形 :都是找树中的 “最长链”
找最长链方法:设 为以 为根的子树,最长的一条链的长度,我们统计答案时用 的两个儿子的 最大值、次大值 链来拼接更新答案,只有这样才不会漏掉答案。
以16题的代码为例:
void dfs(int u,int fa){
int siz=g[u].size();
int maxn=0,sec=0;
for(auto v:g[u]){
if(v==fa) continue;
dfs(v,u);
sec=max(sec,f[v]);
if(sec>maxn) swap(sec,maxn);
}
if(fa!=0) siz--;
ans=max(ans,maxn+sec+siz-min(siz,2)+1+(fa!=0));
f[u]=maxn+siz-min(siz,1)+1;
}
17. P2279 [HNOI2003] 消防局的设立
树形 :这道题可以学到 解决半径为 的最小覆盖问题
设 表示离点 最近的点与之的最短距离,当前点为 ,我们遍历其 层的父亲,用来更新 ,
int p=u;
for(int i=1;i<=K;i++){
p=fa[p];
dis[u]=min(dis[u],dis[p]+i);
}
若最后 ,则说明没有点可以覆盖到当前点 ,因此我们将 的向上第 层父亲 变为可以覆盖其他点的标记点,此时再将 的向上 层父亲,共 个 父亲,将他们的 值重新更新一遍。
遍历时,我们先从深度最深的点开始遍历,之后就是如上的更新 ,每一次加入新的标记点时答案加一
这样复杂度为 。
核心代码:
for(int i=1;i<=n;i++){
int p=a[i].id,u=a[i].id;
for(int j=1;j<=K;j++){
p=fa[p];
dis[u]=min(dis[u],dis[p]+j);
}
if(dis[u]>K){
dis[p]=0;
for(int j=1;j<=K;j++){
p=fa[p];
dis[p]=min(dis[p],j);
}
}
}
18. P3360 偷天换日
树形背包问题:第 12 题的延伸版,改一下代码就行
19. Vlad and the Mountains
重构树模板题:将两点之间的边权定义为 两点的点权的最大值。
20. P9745 「KDOI-06-S」树上异或
树形 将统计方案数字拆解成 位的二进制:
首先设 在以 为根的子树中,对于每一种割边的方法,点 所在连通块异或出来的值在二进制表示下的第 位为 的情况下,其他连通块的异或的乘积之和。
转移时考虑两种状态:1. 将 所在连通块并入 所在连通块 2. 将 作为单独的连通块,不并入 所在连通块。
这题可以学习一下 拆位 做法,重要。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+10,mod=998244353;
int n,f[N][65][2],pow2[65];
ll a[N];
vector<int> g[N];
void dfs(int u){
int tmp[64][2];
for(auto v:g[u]){
dfs(v);
memcpy(tmp,f[u],sizeof(tmp));
memset(f[u],0,sizeof(f[u]));
int ans_v=0;
for(int j=0;j<64;j++)
ans_v=(ans_v+1ll*f[v][j][1]*pow2[j]%mod)%mod;
for(int j=0;j<64;j++){
for(int x=0;x<2;x++){
f[u][j][x]=(f[u][j][x]+1ll*tmp[j][x]*ans_v%mod)%mod;
for(int y=0;y<2;y++)
f[u][j][x^y]=(f[u][j][x^y]+1ll*tmp[j][x]*f[v][j][y]%mod)%mod;
}
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=2;i<=n;i++){
int u;
scanf("%d",&u);
g[u].push_back(i);
}
for(int i=1;i<=n;i++)
for(int j=0;j<64;j++)
f[i][j][a[i]>>j&1]=1;
pow2[0]=1;
for(int i=1;i<=63;i++) pow2[i]=1ll*pow2[i-1]*2%mod;
dfs(1);
int ans=0;
for(int j=0;j<64;j++)
ans=(ans+1ll*f[1][j][1]*pow2[j]%mod)%mod;
printf("%d\n",ans);
return 0;
}
21. P3574 [POI2014] FAR-FarmCraft
树形 :设 表示以 为根的子树中能玩到游戏的最晚时间,即取最大值。设 表示从 出发,将其子树中的所有点遍历完后又回到 ,总共花费的时间。