【学习笔记】2021.10.6 - 清北学堂基础算法应用讲解
大模拟
Luogu P3952
题目传送门
题目内容
- A++ 语言的循环结构如下:
F i x y
循环体
E
- 等同于
for(register int i=x;i<=y;++i){
//循环体
}
-
此外,E 表示循环体结束,同时销毁当前循环变量 i ,变量 i 不可与未销毁的变量重名。
-
题目还会给定一个复杂度,需要判断程序的复杂度与给定复杂度是否一致,输出 YES/NO ,还需判断是否 CE ,如果 CE 只需输出 ERR。
正解
- 模拟就完了!
Luogu P1312
题目传送门
Luogu P7075
题目传送门
正解
- 大模拟,一堆细节
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#define ll long long
using namespace std;
ll D[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}/*存储每月天数*/,rs=97/*400年里有多少闰年*/,Q,G,YEAR,IR,DY;
inline void RLL(ll d,ll y,ll ir){//儒略历
if(ir) D[2]++;//判闰年
ll ld=0,m;
for(register int i=1;i<=12;i++){
ld+=D[i];
if(ld>=d){
m=i;//一定在这个月里
d-=(ld-D[i]);//算出具体的日期
if(y-4713<0)//还在公元前
printf("%lld %lld %lld BC\n",d,m,4713-y);//按要求输出
else
printf("%lld %lld %lld\n",d,m,y-4712);//同上
break;
}
}
if(ir) D[2]--;//勿忘回溯
}
inline void GLGLL(ll d,ll y,ll ir){//格里高利历
if(ir) D[2]++;//判闰年
ll ld=0,m,f=1;
for(register int i=1;i<=12;i++){
ld+=D[i];//逐个锁定
if(ld>=d){//可以锁定月份了
m=i;//一定在这个月里
d-=(ld-D[i]);//算出具体的日期
f=0;
printf("%lld %lld %lld\n",d,m,y+1600);
break;
}
}
if(f) printf("1 1 %lld\n",y+1601);
if(ir) D[2]--;//勿忘回溯
}
int main(){
scanf("%lld",&Q);
while(Q--){
scanf("%lld",&G);
if(G<2299160){
DY=G%(365*4+1),YEAR=G/(365*4+1)*4,IR=1;
if(DY>365) DY-=365,YEAR++,IR=0;
if(DY>365) DY-=365,YEAR++;
if(DY>365) DY-=365,YEAR++;
if(IR==1) DY++;
RLL(DY,YEAR,IR);
}
else{
G+=10;//直接跳过
if(G<=2305457){//以1600为起点格外好做
DY=G%(365*4+1),YEAR=G/(365*4+1)*4,IR=1;
if(DY>365) DY-=365,YEAR++,IR=0;
if(DY>365) DY-=365,YEAR++;
if(DY>365) DY-=365,YEAR++;
if(IR==1) DY++;
RLL(DY,YEAR,IR);
}//照样这么处理,没有奇奇怪怪的东西的
else{
G-=2305458;//减去
DY=G%(365*400+rs),YEAR=G/(365*400+rs)*400,IR=1;
for(register int i=1;i<=399;i++){//一个一个试
if((i%4==0&&i%100!=0)||i%400==0){
if(DY>366){
IR=1;
YEAR++;
DY-=366;
}
else break;
}
else{
if(DY>365){
IR=0;
YEAR++;
DY-=365;
}
else break;//让日期等于0很不讲武德
}
}
if(IR==1) DY++;
GLGLL(DY,YEAR,IR);
}
}
}
return 0;
}
二分
CF1436D
题目传送门
正解
- 二分最少的人。(其实我不会
CF1288D
题目传送门
题目内容
-
给定一个 \(n \times m\) 的矩阵 \(a_{i,j}\) , \(1 \leq i \leq n\) , \(1 \leq j \leq m\) 。
-
你需要找出 \(x,y\) ,使得 \(b_i=max\{a_{x,i},a_{y,i}\}\) 。
-
最大化 \(\min\limits_{1 \leq i \leq m}\{b_i\}\) ,并给出一组方案。
-
数据范围:\(1\leq n \leq 3 \times 10^5\) , \(1 \leq m \leq 8\) , \(0 \leq x,y \leq 10^9\) 。
-
超级加倍版: \(1 \leq m \leq 13\) 。
正解
- 你说呢 。
Luogu P3957
题目传送门
题目内容
- goto 传送门。
正解
-
你说呢 。
-
主要是因为手没有这么快QWQ
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime>
#include<sstream>
#include<list>
#include<bitset>
using namespace std;
const int MAXN(500233);
typedef long long ll;
int n,d,k,l,r,x[MAXN],s[MAXN];//注:l、r表示的是金币的上下限
int q[MAXN];ll f[MAXN];
inline int R(){
int x=0,f=1;char c=' ';
while(c>'9'||c<'0'){c=getchar();if(c=='-') f=-1;}
while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
return x*f;
}
inline void W(int x){
if(x<0) putchar('-'),x=-x;
int y=0,z=0,i=0;
while(x){y=y*10+x%10;z++;x=x/10;}
while(++i<=z){putchar(y%10+'0');y/=10;}putchar('\n');
return;
}
inline bool chk(int coin){//coin个金币能有多少分?
int mn=max(1,d-coin),mx=d+coin,l=0,r=-1,head=1,tail=0;
for(register int i=1;i<=n;++i){
f[i]=-1e18;
while(r+1<i&&(x[i]-x[r+1]>=mn)){//不许跳过,如果能跳?
while(head<=tail&f[q[tail]]<=f[r+1]) tail--;//如果不是最优就扔掉
q[++tail]=++r;//找到了一个可以用来转移的点
}
while(head<=tail&&x[i]-x[q[head]]>mx) head++;//跳不到就应该扔掉
if(head<=tail) f[i]=f[q[head]]+s[i];//找最优点转移
if(f[i]>=k) return true;//能拿到这些分当然可以辣
}
return false;//不然就不行咯
}
int main(){
n=R();d=R();k=R();
for(register int i=1;i<=n;++i){
x[i]=R();
s[i]=R();
}
r=x[n]+1;//最多只能花这些钱
while(l<=r){
int mid=(l+r)>>1;
if(chk(mid)) r=mid-1;//得寸进尺
else l=mid+1;//遭报应
}
W(((l>=(x[n]+1))?-1:l));//涵盖全图也不能达到分数就是不能了
return 0;
}
//P3957 [NOIP2017 普及组] 跳房子
- (难得写了一题QWQ)
Luogu P2680
题目传送门
正解
-
二分最短时间 x ,对于每次 check ,问题就变成了如何保证在把一条边边权归零的情况下保证任意两点间的最短路都 \(\leq\) x 。
-
归零哪条边呢?求出边权\(>\)x的所有边的交集,从交集中选取边权最大的归零即可,这个可以用树上差分实现。
-
注意每条路径的 LCA 可以预处理,不需要每次二分都求一次。
-
时间复杂度 \(O((n+m)\log V)\) 。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime>
#include<sstream>
#include<list>
#include<bitset>
using namespace std;
const int MAXN(3e5+5);
struct edge{int to,next,dist;}ed[MAXN<<1];
struct road{int From,To,Lca,Dist;}rod[MAXN<<1];
int cnt,n,m,u,v,w,sum,tot,num[MAXN],head[MAXN],N_o_t[MAXN],dep[MAXN],tree_sum[MAXN],fa[MAXN][25],f[MAXN][25];
bool vis[MAXN];//基本定义
inline void add(int from,int to,int dist){
ed[++cnt].to=to;ed[cnt].next=head[from];ed[cnt].dist=dist;head[from]=cnt;
}//链式前向星加边
inline int get_lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);int rrd=dep[x]-dep[y];
for(register int i=0;i<=24;++i) if((1<<i)&rrd) x=fa[x][i];
if(x==y) return x;
for(register int i=24;i>=0;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}//倍增lca
inline bool check(int mid){
int tot=0,more=0;memset(N_o_t,0,sizeof(N_o_t));//初始化
for(register int i=1;i<=m;++i) if(rod[i].Dist>mid) N_o_t[rod[i].From]+=1,N_o_t[rod[i].To]+=1,N_o_t[rod[i].Lca]-=2,more=max(more,rod[i].Dist-mid),tot++;//树上差分
for(register int i=n;i>=1;--i) N_o_t[fa[num[i]][0]]+=N_o_t[num[i]];//差分值向父节点累加
for(register int i=2;i<=n;++i) if(N_o_t[i]==tot&&tree_sum[i]-tree_sum[fa[i][0]]>=more) return true;//如果某条边可以换掉就true
return false;
}//判断该解是否满足题意
void DFS(int x,int ffa,int deep){
tot++;num[tot]=x;dep[x]=deep;vis[x]=true;
for(register int i=1;i<25;++i) fa[x][i]=fa[fa[x][i-1]][i-1];//更新父亲
for(register int i=head[x];i;i=ed[i].next){//枚举儿子
int tt=ed[i].to;
if(!vis[tt]) fa[tt][0]=x,tree_sum[tt]=tree_sum[x]+ed[i].dist,DFS(tt,x,deep+1);//继续DFS
}
}//DFS求树上前缀和、深度和DFS序的儿子
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n-1;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);sum+=w;
}DFS(1,0,1);
for(register int i=1;i<=m;++i){
scanf("%d%d",&rod[i].From,&rod[i].To);
rod[i].Lca=get_lca(rod[i].From,rod[i].To);
rod[i].Dist=tree_sum[rod[i].From]+tree_sum[rod[i].To]-2*tree_sum[rod[i].Lca];//求路径i长度
}
int l=0,r=sum,mid;while(l<r){mid=(l+r)>>1;if(check(mid)) r=mid;else l=mid+1;}//二分
printf("%d\n",l);//输出
return 0;
}
CF1517E
题目传送门
正解
- 你说呢。
代码
- 无QWQ
贪心
特征
-
每次都取局部最优解。
-
证明正确性让人很快乐(不是。
Luogu P4753
题目传送门
正解
-
每次跳最近能跳的即可。
-
正确性:看题解好了QWQ
CF1333F
题目传送门
正解
- 看题解好了QWQ
Luogu P5021
题目传送门
正解
- 你说呢。
Luogu P7078
题目传送门
正解
- 你说呢。
Luogu P5665
题目传送门
正解
- 你说呢。
Luogu P3745
题目传送门
正解
- 你说呢。