【集训总结】CSP-S/NOIP 2022 清北学堂 集训总结
END OF OI —— CSP-S/NOIP 2022 清北学堂
Day 1 搜索进阶 模拟赛
埃及分数
迭代加深搜索,每次迭代加深时可以发现下一次的分母一定比上一次大,所以可以从pre(表示上一次的分母)+1开始枚举,然后又能发现用初始的\(\frac{a}{b}\)-每次枚举的\(\frac{1}{i}\)一定要大于等于下一次枚举的值,倒一下柿子可以进一步的减少搜索范围,实质就是迭代加深+剪枝
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1010;
int a,b;
int limit=1;
int ans[maxn];
int now[maxn];
bool get_ans;
int gcd(int x,int y)
{
if(!y) return x;
return gcd(y,x%y);
}
void moon(int &x,int &y)
{
int g=gcd(x,y);
x/=g;
y/=g;
return ;
}
void dfs(int deep,int son,int mo,int pre)
{
int a=son,b=mo;
moon(a,b);
if(deep==1)
{
if(a==1 && b>pre)
{
if(get_ans && b>=ans[1]) return ;
now[1]=b; get_ans=true;
for(int i=1;i<=limit;i++) ans[i]=now[i];
return ;
}
return ;
}
for(int i=pre;i<=ceil((double)b*deep/a);i++)
{
now[deep]=i;
dfs(deep-1,a*i-b,b*i,i);
}
return ;
}
signed main()
{
scanf("%d%d",&a,&b);
moon(a,b);
while(!get_ans)
{
dfs(limit,a,b,1);
limit++;
}
for(int i=limit-1;i>=1;i--)
{
cout<<ans[i]<<" ";
}
return 0;
}
Day 2 基础算法 数据结构
无名の题
小朋友们分糖果,必须遵循两个原则
1,每个小朋友最少要分到1个糖果
2,相邻的小朋友中,如果体重不同,则体重更高的小朋友必须分到更多的糖果
贪心的例题,但之前学校考过
就是因为从左到右扫会由i-1对i产生影响,从右到左扫会由i+1对i产生影响,因此分别按不同顺序扫一遍,统计贡献
AC code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-')
{
f=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n,ans;
int a[maxn];
int c[maxn];
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
}
for(int i=1;i<=n;i++)
{
c[i]=1;
}
for(int i=2;i<=n;i++)
{
if(a[i]>a[i-1] && c[i]<=c[i-1])
{
c[i]=c[i-1]+1;
}
}
for(int i=n-1;i>=1;i--)
{
if(a[i]>a[i+1] && c[i]<=c[i+1])
{
c[i]=c[i+1]+1;
}
}
for(int i=1;i<=n;i++)
{
ans+=c[i];
}
cout<<ans;
return 0;
}
【HEOI 2016】排序
很巧妙的一道题,数据很水导致有些人暴力水过去了
将所有操作离线下来,因为要找第q位的数字,不难想到二分查找
我们令区间内比自己小的数为0,其他数为1,然后用线段树维护,排序时就按照0/1来排(比自己小的数的数量可以用线段树query,比较操作在build时就能完成)
然后看一下当前check的数字上的值是不是1,如果是1就说明慢足,否则return false
然后就是二分查找的板子
每次建树时要记得将lazy_tag初始化
主要考思维,代码难度不高
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int ans;
int n,m,q;
int a[maxn];
struct que
{
int l,r,opt;
}qu[maxn];
struct s_t
{
int l;
int r;
int val;
int tag=-1;;
}t[maxn*4];
void build(int p,int l,int r,int x)
{
t[p].l=l;
t[p].r=r;
if(l==r)
{
t[p].tag=-1;
t[p].val=(a[l]>=x ? 1 : 0);
return ;
}
int mid=(l+r)>>1;
build(p*2,l,mid,x);
build(p*2+1,mid+1,r,x);
t[p].tag=-1;
t[p].val=t[p*2].val+t[p*2+1].val;
}
void push_down(int p)
{
if(t[p].tag>=0)
{
t[p*2].val=(t[p*2].r-t[p*2].l+1)*t[p].tag;
t[p*2+1].val=(t[p*2+1].r-t[p*2+1].l+1)*t[p].tag;
t[p*2].tag=t[p].tag;
t[p*2+1].tag=t[p].tag;
t[p].tag=-1;
}
}
void update(int p,int l,int r,int k)
{
if(l<=t[p].l && t[p].r<=r)
{
t[p].val=k*(t[p].r-t[p].l+1);
t[p].tag=k;
return ;
}
push_down(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) update(p*2,l,r,k);
if(r>mid) update(p*2+1,l,r,k);
t[p].val=t[p*2].val+t[p*2+1].val;
}
int query(int p,int l,int r)
{
if(l<=t[p].l && t[p].r<=r)
{
return t[p].val;
}
push_down(p);
int mid=(t[p].l+t[p].r)>>1;
int ans=0;
if(l<=mid)
{
ans+=query(p*2,l,r);
}
if(r>mid)
{
ans+=query(p*2+1,l,r);
}
return ans;
}
int query_point(int p,int k)
{
if(t[p].l==t[p].r)
{
return t[p].val;
}
push_down(p);
int mid=(t[p].l+t[p].r)>>1;
int ans=0;
if(k<=mid) { return query_point(p*2,k); }
else { query_point(p*2+1,k); }
}
bool check(int x)
{
build(1,1,n,x);
for(int i=1;i<=m;i++)
{
int cnt=query(1,qu[i].l,qu[i].r);
if(qu[i].opt==0)
{
update(1,qu[i].r-cnt+1,qu[i].r,1);
update(1,qu[i].l,qu[i].r-cnt,0);
}
else
{
update(1,qu[i].l,qu[i].l+cnt-1,1);
update(1,qu[i].l+cnt,qu[i].r,0);
}
}
return query_point(1,q);
}
int main()
{
n=read();
m=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
}
for(int i=1;i<=m;i++)
{
qu[i].opt=read();
qu[i].l=read();
qu[i].r=read();
}
q=read();
int l=1,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
{
l=mid+1;
ans=mid;
}
else
{
r=mid-1;
}
}
cout<<ans;
return 0;
}
【POI 2011】PAT-Sticks
数据结构题(也是贪心),首先考虑颜色全都不同的操作,那么我们可以从大到小进行排序,然后每次找到三个相邻的数,判断能不能构成三角形即可
那么如果有好多种颜色,那么我们可以将每个颜色都搞一个大根堆,然后再搞一个最终存ans的PQ,每次可以选所有颜色里最长的那根(堆顶)push进PQ,然后每次取出三个最长的,如果符合直接输出,否则将最长的那根彻底放弃,再选一根同样颜色的最长棍push进PQ来,这样就可以保证颜色互不相同,问题就转化为了所有颜色都不同的情况
AC code
点击查看代码
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstdlib>
#define pii pair<int,int>
using namespace std;
const int maxn=1e6+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
struct node
{
int col,len;
};
priority_queue <int> q[55];
priority_queue <pii> q_ans;
int k;
int main()
{
k=read();
for(int i=1;i<=k;i++)
{
int n=read();
for(int j=1;j<=n;j++)
{
int len=read();
q[i].push(len);
}
}
for(int i=1;i<=k;i++)
{
if(!q[i].empty())
{
int val=q[i].top();
q[i].pop();
q_ans.push(make_pair(val,i));
}
}
while(q_ans.size()>=3)
{
pair<int,int> a=q_ans.top(); q_ans.pop();
pair<int,int> b=q_ans.top(); q_ans.pop();
pair<int,int> c=q_ans.top(); q_ans.pop();
if(c.first>a.first-b.first)
{
cout<<a.second<<" "<<a.first<<" ";
cout<<b.second<<" "<<b.first<<" ";
cout<<c.second<<" "<<c.first<<" ";
exit(0);
}
else
{
q_ans.push(make_pair(b.first,b.second));
q_ans.push(make_pair(c.first,c.second));
if(!q[a.second].empty())
{
int k=q[a.second].top();
q[a.second].pop();
q_ans.push(make_pair(k,a.second));
}
}
}
cout<<"NIE";
return 0;
}
【十二省联考 2019】春节十二响
看到题目就想起了流浪地球
可以先看部分分,有一种情况为链,可以启发我们
为一条链有两种情况
1.根节点为链的一端,那答案就是\(\sum {val_i}\)
2.根节点不为链的一端,那就可以每次找到左右两棵子树中val最大的节点,可以将其放在一个内存段,对答案的贡献就是两个节点者中的max(其实也是一种贪心策略),最后只剩一边有节点时就变成了情况1,统计ans
向普遍的情况进行扩展,我们可以将子树进行合并,完全朴素的合并可以得60pts
考虑像链的情况二一样进行合并,将子树化为堆,每次将siz小的堆合并到siz大的堆里,可以降低复杂度
即启发式合并
然后就像情况2那样统计答案即可
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#define int long long
using namespace std;
const int maxn=2e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,tot,ans;
int val[maxn];
int head[maxn];
vector <int> o;
priority_queue <int> q[maxn];
struct edge
{
int to;
int next;
}e[maxn*2];
void add(int x,int y)
{
tot++;
e[tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void merge(int x,int y)
{
if(q[x].size()<q[y].size()) swap(q[x],q[y]);
while(!q[y].empty())
{
o.push_back(max(q[x].top(),q[y].top()));
q[x].pop(),q[y].pop();
}
while(!o.empty())
{
q[x].push(o.back());
o.pop_back();
}
}
void dfs(int x)
{
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
dfs(to);
merge(x,to);
}
q[x].push(val[x]);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=2;i<=n;i++)
{
int fa=read();
add(fa,i);
}
dfs(1);
while(!q[1].empty())
{
ans+=q[1].top();
q[1].pop();
}
cout<<ans;
return 0;
}
Day 3 数据结构 动态规划
Treeland Tour
数据范围6000,时限5s,(我第一次自己看出来)明显\(O(n^2log(n))\)的时间复杂度
又因为是LIS,不难想到nlog(n)是单调栈优化的序列上LIS的时间复杂度,此处将序列枚举的O(n)变为了遍历(dfs)树的时间复杂度,也就是说O(nlogn)用来转移,又因为根是不固定的,可以用O(n)枚举根,那么整体时间复杂度就是\(O(n^2logn)\)
相对较简单,就是把单调栈优化LIS板子搬到了树上
感觉洛谷难度评分过高
AC code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=6010;
const int INF=INT_MAX;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,tot;
int ans=-INF;
int a[maxn];
int head[maxn];
int stac[maxn];
struct edge
{
int to;
int next;
}e[maxn*2];
void add(int x,int y)
{
tot++;
e[tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void dfs(int x,int fa)
{
int k=lower_bound(stac,stac+n,a[x])-stac;
ans=max(ans,k+1);
int temp=stac[k];
stac[k]=a[x];
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa) continue;
dfs(to,x);
}
stac[k]=temp;
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read();
int v=read();
add(u,v);
add(v,u);
}
for(int i=0;i<=n;i++)
{
stac[i]=INF;
}
for(int i=1;i<=n;i++)
{
dfs(i,0);
}
cout<<ans;
return 0;
}
楼房重建
lxl之前讲过的一道题目,问题可以抽象一下,在坐标系上画出来,把楼顶和原点连接起来,形成许多条正比例函数的直线,我们可以对直线求斜率,不难发现前面一个楼的斜率大于后面一栋楼,那后面的就看不见了
这样一来就成了zhx讲的离线问题,给出序列,问多少对数对满足\(i< j 且 a_i<a_j\)
问题就简单了很多,线段树分情况维护即可
顺便%%%%%两位cdqz爷
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,m;
struct s_t
{
double max_k;
int len;
}t[4*maxn];
double k[maxn];
int get_len(int p,int l,int r,double rul)
{
if(t[p].max_k<=rul)
{
return 0;
}
if(k[l]>rul)
{
return t[p].len;
}
if(l==r)
{
if(k[l]>rul)
{
return 1;
}
else
{
return 0;
}
}
int mid=(l+r)>>1;
if(t[2*p].max_k<=rul)
{
return get_len(p*2+1,mid+1,r,rul);
}
if(t[p*2].max_k>rul)
{
return get_len(p*2,l,mid,rul)+t[p].len-t[p*2].len;
}
}
void change(int p,int l,int r,int x,int y)
{
if(l==x && r==x)
{
t[p].max_k=(double)y/x;
t[p].len=1;
return ;
}
int mid=(l+r)>>1;
if(x<=mid)
{
change(p*2,l,mid,x,y);
}
if(x>mid)
{
change(p*2+1,mid+1,r,x,y);
}
t[p].max_k=max(t[p*2].max_k,t[p*2+1].max_k);
t[p].len=t[p*2].len+get_len(p*2+1,mid+1,r,t[p*2].max_k);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
k[x]=(double)y/x;
change(1,1,n,x,y);
printf("%d\n",t[1].len);
}
return 0;
}
DZY Loves Fibonacci Numbers
傻逼数据结构 就是线段树维护区间加,区间查询,但是不同的就是加上的是斐波那契数列
当时想的思路和C_liar大佬一样,维护前缀和,然后把l给放进标记里
然后被zhx否决了
大概因为一个值可以被修改多次,标记需要合并,但我们的做法明显只是修改了一次
两个斐波那契数列相加还是一个广义斐波那契,而可以发现广义斐波那契有一个性质
设首项为a,第二项为b,第i项可以表示为\(a\times x+b\times y\)
那么我们可以用f[i][0]表示x,f[i][1]表示y,可以用斐波那契的递推式预处理
然后分别预处理前缀和sum[i][0]和sum[i][1]
前i项的和就是\(a\times sum[i][0]+b\times sum[i][1]\)
我们将f和sum数组预处理出来,然后标记把a,b维护进去即可
代码真jb难调
不过确实,对线段树的理解更深了些
AC code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3e5+5;
const int mod=1e9+9;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m;
int a[maxn];
int f[maxn][2];
int sum[maxn][2];
struct s_t
{
int l,r;
int val;
int a,b;
}t[maxn*4];
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(l==r)
{
t[p].val=a[l];
return ;
}
int mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].val=(t[p*2].val+t[p*2+1].val)%mod;
}
void push_down(int p,int l,int r)
{
if(t[p].a || t[p].b)
{
int len=t[p*2].r-t[p*2].l+1;
t[p*2].a=(t[p*2].a+t[p].a)%mod;t[p*2].b=(t[p*2].b+t[p].b)%mod;
t[p*2].val=(t[p*2].val+t[p].a*sum[len][0]%mod+t[p].b*sum[len][1]%mod)%mod;
t[p*2+1].a=(t[p*2+1].a+t[p].a*f[t[p*2+1].l-l+1][0]%mod+t[p].b*f[t[p*2+1].l-l+1][1]%mod)%mod;
t[p*2+1].b=(t[p*2+1].b+t[p].a*f[t[p*2+1].l-l+2][0]%mod+t[p].b*f[t[p*2+1].l-l+2][1]%mod)%mod;
t[p*2+1].val=(t[p*2+1].val+t[p].a*(sum[t[p*2+1].r-l+1][0]-sum[t[p*2+1].l-l][0]+mod)%mod+t[p].b*(sum[t[p*2+1].r-l+1][1]-sum[t[p*2+1].l-l][1]+mod)%mod)%mod;
t[p].a=0;t[p].b=0;
}
}
void update(int p,int l,int r)
{
if(l<=t[p].l && t[p].r<=r)
{
t[p].a=(t[p].a+f[t[p].l-l+1][0]+f[t[p].l-l+1][1])%mod;
t[p].b=(t[p].b+f[t[p].l-l+2][0]+f[t[p].l-l+2][1])%mod;
t[p].val=(t[p].val+sum[t[p].r-l+1][0]-sum[t[p].l-l][0] + mod +sum[t[p].r-l+1][1] + mod -sum[t[p].l-l][1] + mod)%mod;
return ;
}
push_down(p,t[p].l,t[p].r);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) update(p*2,l,r);
if(r>mid) update(p*2+1,l,r);
t[p].val=(t[p*2].val+t[p*2+1].val)%mod;
}
int query(int p,int l,int r)
{
if(l<=t[p].l && t[p].r<=r)
{
return t[p].val%mod;
}
push_down(p,t[p].l,t[p].r);
int mid=(t[p].l+t[p].r)>>1;
int ans=0;
if(l<=mid) ans=(ans+query(p*2,l,r))%mod;
if(r>mid) ans=(ans+query(p*2+1,l,r))%mod;
return ans;
}
signed main()
{
n=read();
m=read();
f[1][0]=1,f[1][1]=0;
f[2][0]=0,f[2][1]=1;
for(int i=3;i<=n;i++)
{
f[i][0]=(f[i-1][0]+f[i-2][0])%mod;
f[i][1]=(f[i-1][1]+f[i-2][1])%mod;
}
for(int i=1;i<=n;i++)
{
sum[i][0]=(sum[i-1][0]+f[i][0])%mod;
sum[i][1]=(sum[i-1][1]+f[i][1])%mod;
}
for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
for(int i=1;i<=m;i++)
{
int opt=read();
int l=read();
int r=read();
if(opt==1)
{
update(1,l,r);
}
else
{
cout<<(query(1,l,r)+mod)%mod<<endl;
}
}
return 0;
}
Day 4 动态规划
收集邮票
期望概率DP,设f[i]表示已经有了i张邮票,还要买邮票数量的期望值
g[i]表示已经有了i张邮票,还要买邮票花的钱的期望值
不难想出f[n]=0,有\(\frac{i}{n}\)的概率是买到已有的邮票,\(\frac{n-i}{n}\)的概率是买到没有买过的邮票
\(f[i]=\frac{i}{n}\times f[i]+\frac{n-i}{n}\times f[i+1]+1\)
化简得
\(f[i]=\frac{n}{n-i}+f[i+1]\)
因为我们要用动态规划求解,要求无后效性,而题目是有后效性的
我们可以转化一下
初始为一块钱,后面比前面贵了一块
g[i]转移时就要加上f[i]、f[i+1]再加上涨的1块钱
\(g[i]=\frac{n-i}{n}\times (g[i+1]+f[i+1])+\frac{i}{n}\times (g[i]+f[i])+1\)
化简TM自己化,LaTeX太shabi了
AC code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+5;
int n;
double f[maxn];
double g[maxn];
int main()
{
cin>>n;
for(int i=n-1;i>=0;i--)
{
f[i]=f[i+1]+(double)(1.0*n/(n-i)*1.0);
g[i]= g[i+1]+f[i+1]+(double)(1.0*i/(1.0*(n-i)))*f[i]+(double)(1.0*n/((n-i)*1.0));
}
printf("%.2lf",g[0]);
return 0;
}
【SDOI 2006】保安站岗
一眼树形DP(都有图了还看不出来?)
每个点只有三种状态
1.自己有保安
2.儿子有保安
3.父亲有保安
dp[x][0/1/2]对应上边的3种状态,则显然
\(dp[x][0]=val[i]+\sum min(dp[to][0],dp[to][1],dp[to][2])\)
dp[x][1]有点复杂,先说dp[x][2]
\(dp[x][2]=\sum min(dp[to][0],dp[to][1])\)
\(dp[x][1]=\sum min(dp[to][0],dp[to][1])+dp[to'][0]\)
意思就是有一个儿子必须配保安
都挺好理解的,挺水的一个题
AC code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1510;
const int INF=INT_MAX;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,tot;
int val[maxn];
int head[maxn];
struct edge
{
int to,next;
}e[maxn*2];
int dp[maxn][4];
void add(int x,int y)
{
tot++;
e[tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void dfs(int x,int fa)
{
dp[x][0]=val[x];
int sum=0;
dp[x][1]=INF;
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa) continue;
dfs(to,x);
sum+=min(dp[to][0],dp[to][1]);
dp[x][0]+=min(dp[to][0],min(dp[to][1],dp[to][2]));
dp[x][2]+=min(dp[to][1],dp[to][0]);
}
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa) continue;
dp[x][1]=min(dp[x][1],sum-min(dp[to][0],dp[to][1])+dp[to][0]);
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
int id=read();
val[id]=read();
int m=read();
for(int i=1;i<=m;i++)
{
int son=read();
add(id,son);
add(son,id);
}
}
dfs(1,0);
int ans=min(dp[1][0],dp[1][1]);
cout<<ans;
return 0;
}
【NOI 2005】聪聪与可可
期望概率经典题,详见这里
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1010;
const double inf=-1.0;
int tot;
double ans;
int n,e_num,c,m;
int ind[maxn];
int head[maxn];
int dis[maxn][maxn];
int nex[maxn][maxn];
double dp[maxn][maxn];
struct node
{
int to;
int next;
}e[2*maxn];
void add(int x,int y)
{
tot++;
e[tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void BFS(int x)
{
queue <int> q;
q.push(x);
dis[x][x]=0;
while(!q.empty())
{
int from=q.front();
q.pop();
for(int i=head[from];i;i=e[i].next)
{
int to=e[i].to;
if(dis[x][to]==-1)
{
dis[x][to]=dis[x][from]+1;
q.push(to);
}
}
}
}
double DFS(int x,int y)
{
if(dp[x][y]!=-1.0)
{
return dp[x][y];
}
if(x==y)
{
return dp[x][y]=0;
}
if(nex[x][y]==y || nex[nex[x][y]][y]==y)
{
return dp[x][y]=1.0;
}
dp[x][y]=0.0;
for(int i=head[y];i;i=e[i].next)
{
dp[x][y]+=DFS(nex[nex[x][y]][y],e[i].to);
}
dp[x][y]=(dp[x][y]+DFS(nex[nex[x][y]][y],y))/(double)(ind[y]+1)+1;
return dp[x][y];
}
int main()
{
cin>>n>>e_num;
cin>>c>>m;
for(int i=1;i<=e_num;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
ind[x]++;
ind[y]++;
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j]=-1.0;
}
}
memset(dis,-1,sizeof(dis));
for(int i=1;i<=n;i++)
{
BFS(i);
}
memset(nex,0x3f3f3f,sizeof(nex));
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=e[j].next)
{
int to=e[j].to;
for(int k=1;k<=n;k++)
{
if(dis[i][k]==dis[to][k]+1 && nex[i][k]>to)
{
nex[i][k]=to;
}
}
}
}
ans=DFS(c,m);
printf("%.3lf",ans);
return 0;
}
【AHOI 2009】中国象棋
怎么说呢,挺水的这个题
乍一看就是个状压DP,事实上有50%是状压
但是一看就是误导类部分分
实际上是区间DP
设dp[i][j][k]表示当前第i行,有j列放了1个棋子,k列放了2个棋子
然后就是暴力转移+分类讨论
也不知道为啥是蓝题
AC code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=110;
const int mod=9999973;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m;
int dp[maxn][maxn][maxn];
int c(int x)
{
return ((x*(x-1))/2)%mod;
}
signed main()
{
n=read();
m=read();
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k+j<=m;k++)
{
dp[i][j][k]+=dp[i-1][j][k];
if(k>=1) dp[i][j][k]+=dp[i-1][j+1][k-1]*(j+1);
if(j>=1) dp[i][j][k]+=dp[i-1][j-1][k]*(m-(j-1)-k);
if(k>=1) dp[i][j][k]+=dp[i-1][j][k-1]*j*(m-j-(k-1));
if(j>=2) dp[i][j][k]+=dp[i-1][j-2][k]*c(m-(j-2)-k);
if(k>=2) dp[i][j][k]+=dp[i-1][j+2][k-2]*c(j+2);
dp[i][j][k]%=mod;
}
}
}
int ans=0;
for(int i=0;i<=m;i++)
{
for(int j=0;j<=m;j++)
{
ans=(ans+dp[n][i][j])%mod;
}
}
cout<<ans;
return 0;
}
【SCOI 2009】奖励关
读题就是概率与期望DP,再看数据范围,明显状压DP
所以这道题是概率期望+状态压缩DP
设dp[i][s]表示第i轮,状态为s,我们显而易见的发现它无法正推,因为正推可能无法到达状态s,所以我们考虑倒推
用state数组将每个限制集合提前预处理出来
转移时就只有两种情况,即能选和不能选
而能选又可以分为选与不选
可以枚举状态,判断能不能选
能选则\(dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<k-1)]+p[k])\)
不能选则\(dp[i][j]+=dp[i+1][j]\)
最后再除n表示期望,挺简单的
AC code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxm=110;
const int maxn=1<<15;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,k;
int p[20],sta[20];
double dp[maxm][maxn];
int main()
{
k=read();
n=read();
for(int i=1;i<=n;i++)
{
int x;
p[i]=read();
while(x=read()) sta[i]=sta[i]|(1<<x-1);
}
for(int i=k;i>=1;i--)
{
for(int j=0;j<(1<<n);j++)
{
for(int l=1;l<=n;l++)
{
if((sta[l] & j)==sta[l])
{
dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<l-1)]+p[l]);
}
else
{
dp[i][j]+=dp[i+1][j];
}
}
dp[i][j]/=n;
}
}
printf("%.6lf",dp[1][0]);
return 0;
}
Day 5 图论
Cheap Robot
zhx找题果然bt,调了2h。。。。。
我们可以变换一下,首先考虑机器人在某个点离它最近的充电站到它的距离,我们将其设置为dis[i],如果对每个点都跑一边最短路,那我就不用干别的了 一个最短路就Game Over了
我们引入超级源点(这个之前涉及过,在差分约束),看自己喜好,这里我将其设为0号点,那么将0号点与其他所有能充电的点连边的边权全部设为0,由于是无向边,则不难想出其他不能充电的点到最近的能充电的点的距离为dis[i],即超级源点到他的最短路,用Dij预处理即可
然后我们会发现,设在这个点的电量为x,那x应该大于等于dis[i],小于等于电池容量c-dis[i](这个上课我口胡对力),所以\(dis_i \leq x \leq c-dis_i\),又因为从i->j,边权为val,那么在i剩余电量为x,则在j剩余电量为x-val,则有
\(dis_j \leq x-val \leq c-dis_j\)
即\(dis_j+val \leq x \leq c-dis_j+val\)
可得\(dis_j+val \leq x \leq c-dis_i\)
即\(dis_j+val \leq c-dis_i\)
\(dis_i+dis_j+val \leq c\)
我们通过预处理出dis[i],那么我们就可以将边权转化为\(dis_i+dis_j+val\),以这个为新的边权,建出它的一棵最小生成树来
那么答案显然是在树上两点间的所有边里最大的边权值,即树上区间最值
由于这里最小生成树是并查集存储的,我们可以新建一棵树,来实现最小生成树
然后就是树上区间最值了,zhx讲的是倍增
但是倍增多麻烦。。。。我直接树剖套线段树
TM define int long long被卡常了,懒得改了,直接吸氧AC
自己注意一下就好
AC code
点击查看代码
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#define int long long
#define pii pair<int,int>
using namespace std;
const int maxm=1e5+5;
const int maxn=4e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int fath[maxn];
bool vis[maxn];
int max_son[maxn];
long long a[maxn];
long long dis[maxm];
long long val[maxn];
int head_tree[maxn];
int n,tot,m,k,q,cnt;
int top[maxn],id[maxn];
int head[maxn],fa[maxn];
int siz[maxn],deep[maxn];
struct edge
{
int to;
int next;
long long val;
}e[maxn*2],tree[maxn*2];
struct s_t
{
int l,r;
int val;
}t[maxn*4];
struct node
{
int from;
int to;
long long val;
}edg[maxn];
void add(int x,int y,int z)
{
tot++;
e[tot].to=y;
e[tot].val=z;
e[tot].next=head[x];
head[x]=tot;
}
void add_tree(int x,int y,int z)
{
tot++;
tree[tot].to=y;
tree[tot].val=z;
tree[tot].next=head_tree[x];
head_tree[x]=tot;
}
bool cmp(node x,node y)
{
return x.val<y.val;
}
void init()
{
n=read();
m=read();
k=read();
q=read();
for(int i=1;i<=m;i++)
{
int u=read();
int v=read();
long long val=read();
add(u,v,val);
add(v,u,val);
edg[i]={u,v,val};
}
}
void dij()
{
for(int i=1;i<=k;i++) add(0,i,0);
priority_queue <pii,vector<pii>,greater<pii> > q;
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[0]=0; q.push(make_pair(dis[0],0));
while(!q.empty())
{
int k=q.top().second;
q.pop();
if(vis[k]) continue;
vis[k]=true;
for(int i=head[k];i;i=e[i].next)
{
int to=e[i].to;
if(vis[to]) continue;
if(dis[to]>dis[k]+e[i].val)
{
dis[to]=dis[k]+e[i].val;
q.push(make_pair(dis[to],to));
}
}
}
}
int find(int x)
{
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
void merger(int x,int y)
{
fa[y]=x;
}
void kruskal()
{
for(int i=1;i<=m;i++)
{
int u=edg[i].from;
int v=edg[i].to;
edg[i].val+=dis[u]+dis[v];
}
for(int i=1;i<=n;i++) fa[i]=i;
sort(edg+1,edg+m+1,cmp);tot=0;
for(int i=1;i<=m;i++)
{
int u=edg[i].from;
int v=edg[i].to;
long long w=edg[i].val;
if(find(u)==find(v)) continue;
merger(find(u),find(v));cnt++;
add_tree(u,v,w);add_tree(v,u,w);
if(cnt==n-1) break;
}
}
void dfs_first(int x,int f)
{
siz[x]=1;deep[x]=deep[f]+1;fath[x]=f;
for(int i=head_tree[x];i;i=tree[i].next)
{
int to=tree[i].to;
if(to==f) continue;
val[to]=tree[i].val;
dfs_first(to,x);
siz[x]+=siz[to];
if(siz[to]>siz[max_son[x]])
{
max_son[x]=to;
}
}
}
void dfs_second(int x,int t)
{
top[x]=t; tot++;
id[x]=tot;
a[tot]=val[x];
if(max_son[x]==0) return ;
dfs_second(max_son[x],top[x]);
for(int i=head_tree[x];i;i=tree[i].next)
{
int to=tree[i].to;
if(to!=max_son[x] && to!=fath[x])
dfs_second(to,to);
}
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(l==r)
{
t[p].val=a[l];
return ;
}
int mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].val=max(t[p*2].val,t[p*2+1].val);
}
long long query(int p,int l,int r)
{
if(l>r) return 0;
if(l<=t[p].l && t[p].r<=r)
{
return t[p].val;
}
int mid=(t[p].l+t[p].r)>>1;
long long ans=0;
if(l<=mid)
{
ans=max(ans,query(p*2,l,r));
}
if(r>mid)
{
ans=max(ans,query(p*2+1,l,r));
}
return ans;
}
long long get_ans(int x,int y)
{
long long ans=0;
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]]) swap(x,y);
ans=max(ans,query(1,id[top[x]],id[x]));
x=fath[top[x]];
}
if(deep[x]>deep[y]) swap(x,y);
ans=max(ans,query(1,id[x]+1,id[y]));
return ans;
}
signed main()
{
init();dij();kruskal();
tot=0; dfs_first(1,0);
tot=0; dfs_second(1,1);
build(1,1,n);
for(int i=1;i<=q;i++)
{
int x=read();
int y=read();
cout<<get_ans(x,y)<<endl;
}
return 0;
}
白雪皑皑
这题不是图论。。。。原题叫疯狂的馒头,和这题一样的,在BZOJ上
这题就是并查集,因为zhx在讲kruskal,就讲了并查集(果然是一个人讲图论和数据结构)
很简单的,其实就是暴力。。。
对的,操作次数1e7,数据范围1e6的题,暴力是是正解
这也告诉我们:暴力出奇迹
但是盲目的暴力肯定不行,每片雪花最后的颜色肯定是取决于最后一次染色,所以我们倒着遍历,用并查集可以标记它是否被染过色,那我们可以做到每片雪花只被遍历一次,复杂度接近O(n),之所以是接近而不等于是因为并查集不完全是O(1)而是反Ackermann,但是基本上可以忽略
AC code
点击查看代码
#include<iostream>
#include<queue>
#include<cstdio>
#include<stack>
#include<vector>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<climits>
using namespace std;
const int MAXN=1000010;
int n,m,p,q;
int color[MAXN];
int father[MAXN];
int find(int x)
{
if(father[x]<0)
{
return x;
}
return father[x]=find(father[x]);
}
int main()
{
cin>>n>>m>>p>>q;
memset(father,-1,sizeof(father));
for(int i=m;i>=1;i--)
{
int mi=(i*p+q)%n+1;
int ma=(i*q+p)%n+1;
if(mi>ma)
{
swap(mi,ma);
}
mi=find(mi);
while(mi<=ma)
{
color[mi]=i;
father[mi]=mi+1;
mi=find(mi+1);
}
}
for(int i=1;i<=n;i++)
{
cout<<color[i]<<endl;
}
return 0;
}
Day 6 图论 数论
卢卡斯定理
Lucas定理 证明过程:
反之亦然同理,推论自然成立,略去过程QED,由上可知证毕
证明就这样结束了,说一下板子怎么写
就是在mod p的情况下求C[n][m],我们可以把n,m拆为p进制,让他俩对齐(位数不够的补零),然后对于第i位的C[n[i]][m[i]],\(ans=\prod c[n[i]][m[i]]\mod p\)
AC code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int t,n,m,p;
int fac[maxn];
int cnt_n,cnt_m;
int p_n[maxn],p_m[maxn];
int qpow(int a,int b,int mod)
{
int ans=1;
for(;b;b>>=1,a=(a*a)%mod)
{
if(b&1) ans=(ans*a)%mod;
}
return ans;
}
int c(int n,int m)
{
if(m>n) return 0;
return fac[n]*(qpow(fac[m],p-2,p)%p)*(qpow(fac[n-m],p-2,p)%p);
}
int lucas(int n,int m)
{
if(!m) return 1;
return lucas(n/p,m/p)%p*c(n%p,m%p)%p;
}
signed main()
{
t=read();
while(t--)
{
n=read();m=read();
p=read();fac[0]=1;
for(int i=1;i<=p;i++)
{
fac[i]=(fac[i-1]*i)%p;
}
cout<<lucas(n+m,n)<<'\n';
}
}
【APIO 2009】抢掠计划
APIO出板子题。。。。 每次抢劫进入一个强连通分量,则可以把里面的钱都抢完,然后让你求最多抢多少,即经过的最大点权
Tarjin+Topo_sort+DP 就好了
缩点时只需要对start进行Tarjan,然后如果起点无法到达的点直接continue掉,否则可能产生影响
AC code
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int s,p,t;
int n,m,tot,ans;
int id[maxn];
stack <int> st;
bool vis[maxn];
int head[maxn];
int t_head[maxn];
int ind[maxn],dp[maxn];
int val[maxn],dfn[maxn];
int bel[maxn],low[maxn];
struct edge
{
int to,next;
}e[maxn*2],topo_e[maxn*2];
void add(int x,int y)
{
e[++tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void add_e(int x,int y)
{
topo_e[++tot].to=y;
topo_e[tot].next=t_head[x];
t_head[x]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++t;
st.push(x);vis[x]=true;
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(!dfn[to])
{
tarjan(to);
low[x]=min(low[x],low[to]);
}
else if(vis[to])
{
low[x]=min(low[x],low[to]);
}
}
if(dfn[x]==low[x])
{
while(st.top()!=x)
{
bel[st.top()]=x;
vis[st.top()]=false;
val[x]+=val[st.top()];
st.pop();
}
st.pop();
bel[x]=x;
vis[x]=false;
}
}
void topo_sort()
{
queue <int> q;
q.push(bel[s]);
dp[bel[s]]=val[bel[s]];
while(!q.empty())
{
int k=q.front();
q.pop();
for(int i=t_head[k];i;i=topo_e[i].next)
{
int to=topo_e[i].to;
dp[to]=max(dp[to],dp[k]+val[to]);
if(--ind[to]==0) q.push(to);
}
}
for(int i=1;i<=n;i++)
{
if(id[i]) ans=max(ans,dp[bel[i]]);
}
}
signed main()
{
n=read();
m=read();
for(int i=1;i<=m;i++)
{
int u=read();
int v=read();
add(u,v);
}
for(int i=1;i<=n;i++) val[i]=read();
s=read(),p=read();
tarjan(s);
for(int i=1;i<=p;i++)
{
int x=read();id[x]++;
}
tot=0;
for(int i=1;i<=n;i++)
{
if(!bel[i]) continue;
for(int j=head[i];j;j=e[j].next)
{
int to=e[j].to;
if(bel[i]!=bel[to])
{
add_e(bel[i],bel[to]);
ind[bel[to]]++;
}
}
}
topo_sort();
cout<<ans;
return 0;
}