文章目录
前言
中规中矩的比赛 想不出形容词了
373AB是水题
372ABD都是小清新且码量较小的好题
372C之前做过了
372E毒瘤几何+组合数学,没有洛谷题解,CF题解完全看不懂,直接弃疗qwq
CF373A Collecting Beats is Fun
Description \text{Description} Description
一共有
4
×
4
=
16
4\times4=16
4×4=16 个格子,每个格子
(
i
,
j
)
(i,j)
(i,j) 需要在
t
i
,
j
t_{i,j}
ti,j 时刻点击(若为星号则不需要点击),你每只手同一时刻只能点击
k
k
k 个格子(注意你有两只手!),求你是否能完成任务。
t
i
,
j
t_{i,j}
ti,j 是一个
1
−
9
1-9
1−9 的数字。
Solution \text{Solution} Solution
水题。
4
×
4
4\times4
4×4 扫一遍,开一个桶记录每个时刻需要点多少个格子,最后判是否有桶超过
2
k
2k
2k 即可。
Code \text{Code} Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e6+100;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,m;
int bac[12];
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();
for(int i=1;i<=4;i++){
for(int j=1;j<=4;j++){
char c;
scanf(" %c",&c);
if(c!='.') bac[c-'0']++;//debug("%d\n",c-'0');
}
}
for(int i=1;i<=9;i++){
if(bac[i]>2*n){
printf("NO\n");return 0;
}
}
printf("YES\n");
return 0;
}
/*
*/
CF373B Making Sequences is Fun
Description \text{Description} Description
定义 S ( i ) = i S(i)=i S(i)=i 的位数,如 S ( 893 ) = 3 , S ( 114514 ) = 6 S(893)=3,S(114514)=6 S(893)=3,S(114514)=6。
数 x x x 的费用为 k × S ( i ) k\times S(i) k×S(i)。
你有
w
w
w 元钱,要从
m
m
m 开始连续添加尽量多的数
(
m
,
m
+
1
,
m
+
2
,
⋯
)
(m,m+1,m+2,\cdots)
(m,m+1,m+2,⋯) 组成一个序列,问这个序列最长有多长。
w
,
m
≤
1
0
16
,
k
≤
1
0
9
w,m\le 10^{16},k\le10^9
w,m≤1016,k≤109
Solution \text{Solution} Solution
小清新模拟。
可以先令
w
←
⌊
w
k
⌋
w\gets \lfloor \dfrac{w}{k}\rfloor
w←⌊kw⌋ 把
k
k
k 的影响去掉。
一直往进位处跳直到不能继续跳位置,然后把剩下的个数求出来,过程中贡献加起来即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e6+100;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n;
ll w,m,k;
inline ll calc(ll x){
int res(0);
while(x) x/=10,++res;
return res;
}
ll mi[20];
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
mi[1]=1;
for(int i=2;i<=18;i++) mi[i]=mi[i-1]*10;
w=read();m=read();k=read();
w/=k;
ll o=calc(m),ans(0);
while((mi[o+1]-m)*o<=w){
ans+=mi[o+1]-m;w-=(mi[o+1]-m)*o;
++o;m=mi[o];
//printf("o=%lld w=%lld ans=%lld m=%lld\n",o,w,ans,m);
}
ans+=w/o;printf("%lld\n",ans);
return 0;
}
/*
*/
CF372A Counting Kangaroos is Fun
Description \text{Description} Description
有
n
n
n 只袋鼠,每只的大小为
s
i
s_i
si,每只大小为
x
x
x 的袋鼠可以装在大小不少于
2
x
2x
2x 的袋鼠的袋子里。
每只袋鼠的袋子最多装一只袋鼠,且装在别的袋鼠袋子里的袋鼠不能继续装袋鼠(不能套娃),求最多能让多少只袋鼠被装入别的袋鼠的袋子。
Solution \text{Solution} Solution
一道本人做的不太好的题。
一开始显然是想贪心,比如从大往小选,每个尽可能装大的之类,但是都可以被轻易的 hack。
然后就写了个垃圾的二分多只 log 艹过去了
下面讲线性的贪心正解。
其实刚才朴素的贪心是可以借鉴的,唯一的错误就在与尽可能装的的那只“大袋鼠”可能用来装别的袋鼠。
证明:
由于装的个数最多肯定不超过
n
/
2
n/2
n/2 个,所以最小的
n
/
2
n/2
n/2 只袋鼠肯定不会装别人。
同时,如果某只袋鼠装了一只不在这
n
/
2
n/2
n/2 只袋鼠里的袋鼠,那它改成这
n
/
2
n/2
n/2 只袋鼠中没有被装的一只(必然存在)也是不劣的。
所以这个贪心是真的。
维护双指针从 n / 2 n/2 n/2 开始尽可能的装大的即可。
Code \text{Code} Code
(双指针的代码题解区很多了,这里就贴的一开始二分的码,也算是另一种思路)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e6+100;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n;
int a[N];
bool check(int k){
int l=1,r=k+1;
while(l<=k){
while(r<n&&a[r]<2*a[l]) ++r;
if(a[r]<2*a[l]) return false;
++l;++r;
}
return true;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();
for(int i=1;i<=n;i++) a[i]=read();
sort(a+1,a+1+n);
int st=0,ed=n/2;
while(st<ed){
int mid=(st+ed+1)>>1;
if(check(mid)) st=mid;
else ed=mid-1;
}
printf("%d\n",n-st);
return 0;
}
/*
*/
CF372B Counting Rectangles is Fun
Description \text{Description} Description
给定一个
n
∗
m
{n * m}
n∗m 的
0
/
1
0/1
0/1 矩阵,
q
q
q 次询问, 每次询问指定一个子矩形, 求该子矩形种有多少个只包含
0
0
0 的子矩阵。
n
,
m
≤
40
,
q
≤
3
×
1
0
5
n,m\le40,q\le3\times 10^5
n,m≤40,q≤3×105
Solution \text{Solution} Solution
小清新水紫。
似乎已经把
O
(
n
m
q
)
O(nmq)
O(nmq) 的复杂度写在数据范围上了…
考虑枚举这个矩形的长和宽,那么剩下的就只能
O
(
1
)
O(1)
O(1) 查询。
不难想到前缀和。
定义一个矩阵的坐标为右下点的坐标。
按照这个坐标的定义对于每个长和宽
(
a
,
b
)
(a,b)
(a,b) 都 预处理出矩形个数的前缀和,复杂度
O
(
n
2
m
2
)
O(n^2m^2)
O(n2m2)。
那么我们如果在
(
x
1
,
y
1
,
x
2
,
y
2
)
(x1,y1,x2,y2)
(x1,y1,x2,y2) 的矩形中询问大小为
a
×
b
a\times b
a×b 的矩形,就相当于查询坐标在
(
x
1
+
a
−
1
,
y
1
+
b
−
1
,
x
2
,
y
2
)
(x1+a-1,y1+b-1,x2,y2)
(x1+a−1,y1+b−1,x2,y2) 这个矩形内的
a
×
b
a\times b
a×b 的矩形的总个数。
总时间复杂度
O
(
n
2
m
2
+
q
n
m
)
O(n^2m^2+qnm)
O(n2m2+qnm)。
PS:本题由于那个四维前缀和数组跨度太大,请务必**把记录长宽的两维开在后面!**这样在回答询问的时候始终是连续访问,否则会由于过多的 cache miss 而超时…(至少对于我的垃圾码是这样)改完后跑的飞快毫无压力。
后来看题解似乎有 O ( n 2 m 2 ) − O ( 1 ) O(n^2m^2)-O(1) O(n2m2)−O(1) 的神奇偏序前缀和魔法操作,也很妙。
Code \text{Code} Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e6+100;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,m,q;
int sum[50][50],num[50][50][50][50];
inline int Sum(int x1,int y1,int x2,int y2){
return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
}
/*inline int Num(int a,int b,int x1,int y1,int x2,int y2){
return num[a][b][x2][y2]-num[a][b][x1-1][y2]-num[a][b][x2][y1-1]+num[a][b][x1-1][y1-1];
}*/
#define Num(a,b,x1,y1,x2,y2) num[x2][y2][a][b]-num[x1-1][y2][a][b]-num[x2][y1-1][a][b]+num[x1-1][y1-1][a][b]
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read();m=read();q=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) scanf("%1d",&sum[i][j]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
for(int a=1;a<=n;a++){
for(int b=1;b<=m;b++){
for(int i=a;i<=n;i++){
for(int j=b;j<=m;j++)
num[i][j][a][b]=num[i-1][j][a][b]+num[i][j-1][a][b]
-num[i-1][j-1][a][b]+(Sum(i-a+1,j-b+1,i,j)==0);
}
}
}
//debug
for(int i=1;i<=q;i++){
int x1=read(),y1=read(),x2=read(),y2=read();
int ans(0);
for(int a=1;a<=x2-x1+1;a++){
for(int b=1;b<=y2-y1+1;b++){
ans+=Num(a,b,x1+a-1,y1+b-1,x2,y2);
}
}
printf("%d\n",ans);
}
return 0;
}
/*
*/
CF372D Choosing Subtree is Fun
Description \text{Description} Description
有一棵 n n n 个结点的树,树上结点从 1 1 1 到 n n n 标号。
定义树上一个连通子图的权值为最长的区间 [ l , r ] [l,r] [l,r] 的长度,满足标号在 [ l , r ] [l,r] [l,r] 之间的结点均在这个连通子图中。
现在请你求出树上所有的结点数量不超过 k k k 的连通子图的权值最大值。
Solution \text{Solution} Solution
大结论题。
做完本题可以水一下这个。
结论:一棵给定的树,若给出若干关键点,将其按照 dfs 序排序后为
a
1...
k
a_{1...k}
a1...k,则它们的最小导出子图的边权和为
d
i
s
(
a
1
,
a
2
)
+
d
i
s
(
a
2
,
a
3
)
+
.
.
.
+
d
i
s
(
a
k
,
a
1
)
dis(a_1,a_2)+dis(a_2,a_3)+...+dis(a_k,a_1)
dis(a1,a2)+dis(a2,a3)+...+dis(ak,a1) 的一半。
证明:每条边都会被算两遍。(感性理解一下)
有这个结论后,本题就简单了。二分答案,然后双指针扫一遍,不断维护一个 set 计算当前导出图的边权和即可,点数就是再加一。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1e5+100;
inline ll read(){
ll x(0),f(1);char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,m;
struct node{
int to,nxt;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y){
p[++cnt]=(node){y,fi[x]};fi[x]=cnt;
}
int pl[N][20],pos[N],siz[N],tim,dep[N];
void dfs(int x,int fa){
pos[x]=++tim;siz[x]=1;
dep[x]=dep[fa]+1;
pl[x][0]=fa;
for(int k=1;pl[x][k-1];k++) pl[x][k]=pl[pl[x][k-1]][k-1];
for(int i=fi[x];~i;i=p[i].nxt){
int to=p[i].to;
if(to==fa) continue;
dfs(to,x);
siz[x]+=siz[to];
}
return;
}
inline int Lca(int x,int y){
if(pos[x]<=pos[y]&&pos[y]<=pos[x]+siz[x]-1) return x;
for(int k=17;k>=0;k--){
int o=pl[x][k];
if(!o||(pos[o]<=pos[y]&&pos[y]<=pos[o]+siz[o]-1)) continue;
x=pl[x][k];
//printf("k=%d x=%d\n",k,x);
}
return pl[x][0];
}
inline int dis(int x,int y){
int lca=Lca(x,y);
//printf(" dis: x=%d y=%d lca=%d res=%d\n",x,y,lca,dep[x]+dep[y]-2*dep[lca]);
return dep[x]+dep[y]-2*dep[lca];
}
struct cmp{
bool operator ()(const int a,const int b){return pos[a]<pos[b];}
};
set<int,cmp>s;
set<int,cmp>::iterator it;
int now;
inline int Pre(int x){
it=s.find(x);
if(it==s.begin()) return (*s.rbegin());
else{
it--;return (*it);
}
}
inline int Nxt(int x){
it=s.find(x);
it++;
if(it==s.end()) return (*s.begin());
else return (*it);
}
inline void add(int x){
if(s.empty()){
s.insert(x);return;
}
s.insert(x);
int pre=Pre(x),nxt=Nxt(x);
now-=dis(pre,nxt);
now+=dis(pre,x)+dis(x,nxt);
return;
}
inline void del(int x){
if(s.size()==1){
s.erase(x);return;
}
int pre=Pre(x),nxt=Nxt(x);
now-=dis(pre,x)+dis(x,nxt);
now+=dis(pre,nxt);
s.erase(x);
return;
}
bool check(int k){
now=0;s.clear();
for(int i=1;i<=k;i++) add(i);
if(now/2+1<=m) return true;
//printf("(%d %d) now=%d\n",1,k,now);
for(int i=k+1;i<=n;i++){
del(i-k);add(i);
//printf("(%d %d) now=%d\n",i-k+1,i,now);
if(now/2+1<=m) return true;
}
return false;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
memset(fi,-1,sizeof(fi));cnt=-1;
n=read();m=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
addline(x,y);addline(y,x);
}
dfs(1,0);
//printf("lca=%d\n",Lca(2,1));
//printf("check=%d\n",check(3));
//return 0;
int st=1,ed=n;
while(st<ed){
int mid=(st+ed+1)>>1;
if(check(mid)) st=mid;
else ed=mid-1;
}
printf("%d\n",st);
return 0;
}
/*
*/