CodeForces:372(div1)&div373(div2)

前言

中规中矩的比赛 想不出形容词了
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 19 的数字。

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,m1016,k109

Solution \text{Solution} Solution

小清新模拟。
可以先令 w ← ⌊ w k ⌋ w\gets \lfloor \dfrac{w}{k}\rfloor wkw 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} nm 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,m40,q3×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+a1,y1+b1,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;
}
/*
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值