CodeForces:103(div1)&104(div2)

前言

比较水的远古比赛
104A、103A、103B是水题
103C是兼有细节处理和思维含量
103D是小清新简单分块题
103E是神仙网络流(强烈推荐)

CF104A Blackjack

Description \text{Description} Description

Blackjack 是一个扑克牌游戏。

Blackjack 使用除了两张王以外的全部 52 张卡牌,也就是 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , J , Q , K , A 2,3,4,5,6,7,8,9,10,J,Q,K,A 2,3,4,5,6,7,8,9,10,J,Q,K,A。其中规定 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 2,3,4,5,6,7,8,9,10 2,3,4,5,6,7,8,9,10 的点数为 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 2,3,4,5,6,7,8,9,10 2,3,4,5,6,7,8,9,10 J , Q , K J,Q,K J,Q,K 的点数均为 10 10 10 A A A 的点数同时为 1 1 1 11 11 11,这取决于玩家的意愿。虽然扑克牌有花色,但是一张卡牌的点数与其花色无关。这个游戏的规则很简单:拿两张牌,如果这两张牌的点数之和等于 n n n,玩家就赢了,否则玩家就输了。

现在玩家已经拿了一张黑桃 Q Q Q,求在其他牌中再抽一张,能使玩家赢得游戏的方案数。

Solution \text{Solution} Solution

垃圾水题。
分情况特判一下即可。

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
#define ll long long
#define ui unsigned int
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,k,m;

signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  //debug("sdsa");
  n=read()-10;
  if(n<=0) printf("0");
  else if(n<=9) printf("4");
  else if(n==10) printf("15");
  else if(n==11) printf("4");
  else printf("0");
  return 0;
}
/*
*/

CF103A Testing Pants for Sadness

Description \text{Description} Description

有个人要做 n n n 道选择题,必须按 1 ∼ n 1\sim n 1n 的顺序答题,第 i i i 题有 a i a_i ai 个选项。不幸的是,这些题这个人一道也不会,只能猜选项,但是他的记忆非常好,可以记住所有题曾经的正确选项。当他做错一道题时,他就必须从 1 1 1 重新开始选,假设题目的正确选项不会变,在最坏的情况下,若要做对所有题,他一共选了多少次选项?

1 ≤ n ≤ 100 , 1 ≤ a i ≤ 1 0 9 1\leq n\leq 100,1\leq a_i \leq 10^9 1n100,1ai109

Solution \text{Solution} Solution

个人心中签到题的榜样。
没码量,又有一点点思维。
对于第 i i i 关单独考虑考虑。
首先,每个错误选项都会且只会选一次,也就是 1 × ( a i − 1 ) 1\times(a_i-1) 1×(ai1)
对于正确选项,它会被选做到这一关的次数,换句话说就是之后答错的次数 + 1 +1 +1,也就是 ∑ j = i + 1 n ( a j − 1 ) + 1 \sum_{j=i+1}^n(a_j-1) +1 j=i+1n(aj1)+1
全加起来即可,后缀和优化一下,时间复杂度 O ( n ) O(n) O(n)

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
#define ll long long
#define ui unsigned int
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,k,m;
ll a[N],suf[N],ans;
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();
  suf[n+1]=1;
  for(int i=n;i>=1;i--) suf[i]=suf[i+1]+a[i]-1;
  for(int i=1;i<=n;i++) ans+=a[i]-1+suf[i+1];
  printf("%lld\n",ans);
  return 0;
}
/*
*/

CF103B Cthulhu

Description \text{Description} Description

给出一个图,判断它是不是一个环的大小不小于 3 3 3 的奇环树。

Solution \text{Solution} Solution

这题的唯一难度可能仅在于耐着性子把英语题面看完…
看懂题面后就几乎没有难度了。
dfs 一遍即可。
别忘了判连通性。
(后来看题解才想起来本题根本就没有重边,所以根本不需要 dfs,并查集判一下连通性就可以了。)

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
#define ll long long
#define ui unsigned int
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 flag;
int vis[N];
vector<int>v[N];
void dfs(int x,int fa){
  vis[x]=1;
  for(int to:v[x]){
    if(to==fa) continue;
    if(vis[to]) flag=1;
    else dfs(to,x);
  }
  return;
}
signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  n=read();m=read();
  if(m!=n){
    printf("NO");return 0;
  }
  for(int i=1;i<=m;i++){
    int x=read(),y=read();
    v[x].push_back(y);v[y].push_back(x);
  }
  dfs(1,0);
  if(!flag) printf("NO");
  else{
    for(int i=1;i<=n;i++){
      if(!vis[i]){
	printf("NO");return 0;
      }
    }
    printf("FHTAGN!");
  }
  return 0;
}
/*
*/

CF103C Russian Roulette

Description \text{Description} Description

两个傻瓜玩俄罗斯轮盘(轮流开枪,直到一方死亡), n n n 个位置里有 k k k 个子弹,请你填装这 k k k 个子弹,使先开枪者死亡的概率最小,在满足该条件的情况下最小化方案的字典序(空弹夹字典序更小)。
每次询问位置 x x x 是否装有子弹。
k ≤ n ≤ 1 0 18 k\le n\le10^{18} kn1018

Solution \text{Solution} Solution

有点小细节的一道题。
(样例真心良心)
有子弹的地方先手必然死,所以我们就是让空弹夹先手死的尽可能少就行了。

通过观察样例二可以发现,从后往前,隔一个放一颗子弹是一种很好的方案。
但是观察样例三可以发现,在刚才那种构造的基础上,有的时候可以把一枚最前面的子弹挪到后面挨着放,死亡概率不变,但字典序更小。
具体的,这种情况是在前面空出的连续空白段长度为偶数时成立,这时候前面去掉一个必胜位置的数量不变。

还有一些其它边边角角的情况,在 2 k ≥ n 2k\ge n 2kn 时,后面挨着放前面隔着放即可;还要注意 n = 1 n=1 n=1 的时候不能“把最前面的一个挪到后面挨着放”(因为一共就只有一个)

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=2e5+100;
#define ll long long
#define ui unsigned int
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;
}

ll n,k,m;
int op;
signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  n=read();k=read();m=read();
  ll pl(0);
  if(k*2>=n){
    op=1;pl=2*(n-k);
  }
  else if((n-2*k)%2==0||k<=1){
    op=2;pl=n-2*k;
  }
  else{
    op=3;pl=2*k-2;
  }
  for(int i=1;i<=m;i++){
    ll x=read();
    if(op==1){
      if(x>pl||(x&1)==0) printf("X");
      else printf(".");
    }
    else if(op==2){
      if(x<=pl||((x-pl)&1)) printf(".");
      else printf("X");
    }
    else{
      x=n-x+1;
      if(x<=2||(x<=pl&&x%2==0)) printf("X");
      else printf(".");
    }
  }
  return 0;
}
/*
*/

CF103D Time to Raid Cowavans

Description \text{Description} Description

一个序列 a a a m m m 次询问,每次询问给出 t , k t, k t,k。求 a t + a t + k + a t + 2 k + ⋯ + a t + p k a_t + a_{t+k}+a_{t+2k}+\cdots+a_{t+pk} at+at+k+at+2k++at+pk 其中 t + p k ≤ n t+pk \leq n t+pkn t + ( p + 1 ) k > n t+(p+1)k > n t+(p+1)k>n

n , m ≤ 300000 , a i ≤ 1 0 9 n,m \leq 300000,a_i \leq 10^9 n,m300000,ai109

Solution \text{Solution} Solution

似乎题解有对前缀和进行分块优化空间从而在线的神仙做法?
但是这题离线就挺香的了
不难想到根号分治,对于 k > n k>\sqrt n k>n 的询问,直接暴力即可。
对于 k ≤ n k\le \sqrt n kn 的询问,离线下来对于 n \sqrt n n 个可能的 k k k 分别处理前缀和就可以 O ( 1 ) O(1) O(1) 回答所有询问。
时间复杂度 O ( n n ) O(n\sqrt n) O(nn ),空间复杂度 O ( n ) O(n) O(n)

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=3e5+100;
#define ll long long
#define ui unsigned int
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;

ll sum[N],a[N];
struct query{
  int st,d,id;
  bool operator < (const query o)const{return d<o.d;}
}q[N];
int tot;
ll ans[N];
signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  n=read();
  int w=sqrt(n);
  for(int i=1;i<=n;i++) a[i]=read();
  m=read();
  for(int i=1;i<=m;i++){
    int aa=read(),b=read();
    if(b>w){
      ll res(0);
      for(int j=aa;j<=n;j+=b) res+=a[j];
      ans[i]=res;
    }
    else q[++tot]=(query){aa,b,i};
  }
  sort(q+1,q+1+tot);
  int now(0);
  for(int i=1;i<=tot;i++){
    if(now!=q[i].d){
      now=q[i].d;
      for(int j=1;j<=n;j++){
	sum[j]=a[j];
	if(j>now) sum[j]+=sum[j-now];
      }      
    }
    int id=q[i].id,st=q[i].st,d=q[i].d,ed=st+(n-st)/d*d;
    ans[id]=sum[ed];
    if(st>now) ans[id]-=sum[st-now];
  }
  for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
  return 0;
}
/*
*/

CF103E Buying Sets

Description \text{Description} Description

有一个大小为 n n n 的全集,每个元素是一个数,有 n n n 个子集。题目保证任意 k k k 个子集的并的大小 ⩾ k \geqslant k k

每个子集有一个可正可负的权值,你需要选出一些子集使得这些子集并的大小等于子集个数,且所选子集的权值和最小。可以为空集。
n ≤ 300 n\le 300 n300

Solution \text{Solution} Solution

一道网络流比较神的题。
首先可以补集转化,选的权值最小,转化为不选的权值最大。
然后由于这种题大多数都是转成最小割,所以把所有权值取反。

然后就是关键的建图:
原点向所有集合连容量等于集合权值 I N F INF INF 的边,集合向包含的数字连 I N F INF INF 的边,数字向汇点连 I N F INF INF 的边。
显然,断数字和集合之间的边是不优的;而其中集合断边相当于不选,数字断边相当于选。
这样,考虑任意一种割的方案,都说明选择了所有的集合和他们的数字的并集。

但是怎么保证数字数量等于集合数量呢?
假设左边断了 x x x 条边,剩的 n − x n-x nx 个集合必然连向不少于 n − x n-x nx 个数,所以右边断边不少于 n − x n-x nx 条,总断边数不少于 n n n 条。
同时,只断 n n n 条的方案显然存在(只砍一边就行),又由于每条边的权值加上了 I N F INF INF,所以显然最终的答案就会断 n n n 条。
回到刚才断边的定义,就是:未选集合+选取元素=n,也就是选取集合数等于选取元素数。
得证。

Description \text{Description} Description

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=3e5+100;
#define ll long long
#define ui unsigned int
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;

#define inf 1000000000ll
struct node{
  int to,nxt,cap;
}p[N];
int fi[N],cur[N],cnt;
inline void addline(int x,int y,int w){
  p[++cnt]=(node){y,fi[x],w};fi[x]=cnt;
  p[++cnt]=(node){x,fi[y],0};fi[y]=cnt;
  return;
}
int s,t;
int bel[N],q[N],st,ed;
bool bfs(){
  q[st=ed=1]=s;
  memset(bel,0,sizeof(bel));bel[s]=1;
  while(st<=ed){
    int now=q[st++];
    for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
      int to=p[i].to;
      if(!p[i].cap||bel[to]) continue;
      bel[to]=bel[now]+1;q[++ed]=to;
    }
  }
  return bel[t]?1:0;
}
int dfs(int x,int lim){
  if(!lim||x==t) return lim;
  int res(0);
  for(int &i=cur[x];~i;i=p[i].nxt){
    int to=p[i].to;
    if(!p[i].cap||bel[to]!=bel[x]+1) continue;
    int add=dfs(to,min(lim,p[i].cap));
    res+=add;lim-=add;
    p[i].cap-=add;p[i^1].cap+=add;
    if(!lim) break;
  }
  if(!res) bel[x]=-1;
  return res;
}
ll dinic(){
  ll flow(0),tmp(0);
  while(bfs()){
    while((tmp=dfs(s,2e9))) flow+=tmp;
  }
  return flow;
}

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();
  ll sum(0);
  s=2*n+1;t=s+1;
  for(int i=1;i<=n;i++) addline(i+n,t,inf);
  for(int i=1;i<=n;i++){
    int k=read();
    for(int j=1;j<=k;j++){
      int x=read();
      addline(i,x+n,inf);
    }
  }
  for(int i=1;i<=n;i++){
    int w=read();sum-=w;
    addline(s,i,inf-w);
  }
  printf("%lld\n",(dinic()-1ll*n*inf)-sum);
  return 0;
}
/*
*/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值