Codeforces#547-549div.2后三题题解

Codeforces Round #549

D. The Beatles(数论题)

【题目描述】

题目描述

E.Lynyrd Skynyrd(好题)

【题目描述】

题目链接

给定一个长度为n的模板序列,是1-n的某个排列;给定一个长度为m的序列a,现在有q次询问,每次询问序列a从li到ri是否存在一个非连续子串可以构成模板序列,是则输出1,否则输出0,答案为一串01序列

【思路】

区间查询,但查询的方式类似于字符串匹配,不像是线段树或者dp那样可以转移的东西,看了题解才知道居然是用倍增的方法来优化而已
倍增算法有好几个博客讲的很好
(https://blog.csdn.net/dong_qian/article/details/81702697)
遇到这种非连续的子串,好多都是记录一下模板串的下标,用pos记录一下,然后读入序列a的时候,在线处理一下当前这个数在模板串中的前一个数在a的最近一次出现位置,用lst来记录最近出现的那个位置,这是一个贪心的思想,从离得最近的那个蹦过来可以让完成匹配的区间尽可能小
然后用倍增数组处理一下蹦20,21,22…可以到达的位置,用f记录
再用另一个倍增数组G[i][j]记录序列a从i到i+2j这个区间所有数向左蹦n-1次可以到达的位置中最靠右的
然后处理每个查询的时候,把(l,r)区间一分为二(内部有重叠没关系),看看两个区间内G的值是否有未越界的

贴上蒟蒻代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
int n,m,q,a[200010],b[200010],pos[200010],lst[200010];
int f[200010][30],G[200010][30],L[200010];
int amax(int a,int b){
 return a>b?a:b;
}
int main(){
 freopen("E.in","r",stdin);
 freopen("E.out","w",stdout);
 int i,j,cnt,poss,t,l,r;
 scanf("%d%d%d",&n,&m,&q);
 for(i=1;i<=n;i++){
  scanf("%d",&a[i]); 
  pos[a[i]]=i;
 }
 for(i=1;i<=m;i++){
  scanf("%d",&b[i]);
  t=a[pos[b[i]]-1];
  if(!t) t=a[n];
  f[i][0]=lst[t];
  lst[b[i]]=i;
 }
 for(i=1;1<<i<=n;i++){
  for(j=1;j<=m;j++){
   f[j][i]=f[f[j][i-1]][i-1];
  }
 }
 for(i=1;i<=m;i++){
  poss=i;
  cnt=n-1;
  for(j=20;j>=0;j--){
   if(cnt-(1<<j)>=0){
    cnt-=1<<j;
    poss=f[poss][j];
   }
  }
  G[i][0]=poss;
 }
 for(j=1;(1<<j)<=m;j++){
  for(i=1;i+(1<<j)-1<=m;i++){
   G[i][j]=amax(G[i][j-1],G[i+(1<<(j-1))][j-1]);
  }
 }
 L[0]=-1;
 for(i=1;i<=m;i++) L[i]=L[i>>1]+1;
 for(i=1;i<=q;i++){
  scanf("%d%d",&l,&r);
  cnt=L[r-l+1];
  if(amax(G[l][cnt],G[r-(1<<cnt)+1][cnt])>=l) printf("1");
  else printf("0");
 } 
 return 0;
} 

F.U2

【题目描述】

题目链接

给定n个坐标,两两之间可确定一个抛物线y=x^2+bx+c,找出最多组抛物线,使得剩余的点全都在抛物线外(下方)

【思路】

一开始想区间dp,不过n的规模显然是要爆内存的,然后突然觉得这不是很像凸包嘛(这么明显我都没看出来,还是看了cf的tag才发现,太菜 ),但是平方项真的太恶心了
原问题相当于y1=x12+b0x1+c0 y2=x22+b0x2+c0
若(x3,y3)在y=x2+bx+c的内部,则y3>x32+b0x3+c0
由于轨迹方程只和b,c有关,二次项系数始终为1,故将平方项移至左边
y3-x32>b0x3+c0
令y3’=y3-x32
即(x3,y3’)在(x1,y1’) (x2,y2’)的上方
故转化为凸包问题

这是一个向上凸的凸包,想象起始点在最左边那个点的y轴负方向无穷远处,故极角的排序转化为按x坐标升序,y坐标降序来排序
注意:由于是上凸,所以to_left判断为真(在左侧)时要出栈,在同一条线上时也要出栈,且x坐标相同时要去重
cf上有一个数据是n=1,所以特判一下

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
int s[200010],cnt,n,ans;
struct node{
 long long x,y;
 bool operator < (const node & tmp) const{
     return x!=tmp.x?x<tmp.x:y>tmp.y;
 }
}p[2000010];
bool to_left(node& a,node& b,node& c){
    if((b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)<0) return false;
    return true;
}
int main(){
 freopen("F.in","r",stdin);
 freopen("F.out","w",stdout);
 int i;
 scanf("%d",&n);
 if(n==1){
  printf("0\n");
  return 0;
 }
 for(i=1;i<=n;i++){
  scanf("%I64d%I64d",&p[i].x,&p[i].y);
  p[i].y-=p[i].x*p[i].x;
 }
 sort(p+1,p+n+1);
 s[++cnt]=1;
 s[++cnt]=2;
 for(i=3;i<=n;i++){
  while(cnt>1&&to_left(p[s[cnt-1]],p[s[cnt]],p[i])){
   cnt--;
     }
     s[++cnt]=i;
 }
    for(i=1;i<cnt;i++){
     if(p[s[i]].x!=p[s[i+1]].x) ans++;
 }
 printf("%d\n",ans);
 return 0;
}

Codeforces Round #548 div.2

D.Step to One

【题目描述】

题目链接

【思路】

期望DP
正向推概率,反向推期望
概率DP/期望DP刷题总结
ACM概率期望DP总结
莫比乌斯反演入门讲解
这道题做了两个星期才A,因为没有接触过概率/期望DP,还在kuangbin上做了一些题,另外这题也是学习莫比乌斯反演的好题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAX=100000;
const ll P=1000000007;
ll mo[MAX+5],dp[MAX+5],prime[MAX+5],m,ans;
int vis[MAX+5],cnt;
void get_mo(){//求莫比乌斯函数 
 memset(vis,0,sizeof(vis));
 memset(mo,0,sizeof(mo));
 mo[1]=1;
 cnt=0;
 for(int i=2;i<=MAX;i++){
  if(!vis[i]){
   prime[cnt++]=i;
   mo[i]=-1;
  }
  for(int j=0;j<cnt&&prime[j]*i<=MAX;j++){
   vis[prime[j]*i]=1;
   if(i%prime[j]==0) break;
   mo[prime[j]*i]=-mo[i];
  }
 }
}
ll pow(ll a,ll b,ll P){//求逆元 
 if(b==0) return 1;
 ll cnt;
   cnt=pow(a,b/2,P);
 cnt=cnt*cnt%P;
 if(b%2==1) cnt=cnt*a%P;
 return cnt;
}
ll cal(ll x,ll mm){ 
 ll kk=0;
 for(int i=1;i*i<=x;i++){
  if(x%i==0){
   kk+=mo[i]*(mm/i)%P;
   kk%=P;
   if(x/i!=i){
    kk+=mo[x/i]*(mm/(x/i))%P;
    kk%=P;
   }
  }
 }
 return kk;
}
int main(){
 freopen("DD.in","r",stdin);
 freopen("DD.out","w",stdout);
 get_mo();
 scanf("%I64d",&m);
 dp[1]=0;
 for(int i=2;i<=m;i++){
  dp[i]=m;
  for(int j=2;j*j<=i;j++){
   if(i%j==0){
    dp[i]+=(cal(i/j,m/j)*dp[j]%P);
    dp[i]%=P;
    if(j!=i/j){
     dp[i]+=(cal(j,m/(i/j))*dp[i/j]%P);
     dp[i]%=P;
    }
   }
  }
  dp[i]=dp[i]*pow(m-m/i,P-2,P)%P;
  dp[i]%=P;
 }
 ans=m;
 for(int i=1;i<=m;i++)
     ans=(ans+dp[i])%P; 
 ans=ans*pow(m,P-2,P)%P;
 ans%=P;
 printf("%I64d\n",ans);
 return 0;
} 

E.Maximize Mex

【题目描述】

有n个人,第i个人有pi潜力值,属于第ci个俱乐部,现在在d天之内,每天有一个人退出,每天,从每一个有人的俱乐部里选择一个人,他们的潜力值从0开始,找到最小的那个没有出现的自然数使它尽可能大

【思路】

二分图求mex是一种常用技巧 貌似?
二分图匹配的建图方法详见我的另一篇博客
本题采用的是行列式,建立pc的对应关系,只需要找到从0开始连续的最大可覆盖的值,而本题的另一个技巧是将删人改为加人,倒着加即可,因为二分图匹配没有删边的操作
dfs用来寻找增广路

下面附上代码

#include <bits/stdc++.h>
using namespace std;
const int MAX=5000;
vector edge[MAX+5];
int p[MAX+5],c[MAX+5],lft[MAX+5],used[MAX+5],vis[MAX+5],cp[MAX+5],ans[MAX+5];
int n,m,d,mex,dfn;
bool dfs(int x){
for(int i=0;i<edge[x].size();i++){
int j=edge[x][i];
if(used[j]!=dfn){
used[j]=dfn;
if(cp[j]==-1||dfs(cp[j])){
cp[j]=x;
return true;
}
}
}
return false;
}
int main(){
memset(cp,-1,sizeof(cp));
int i;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&p[i]);
for(i=1;i<=n;i++)
scanf("%d",&c[i]);
scanf("%d",&d);
for(i=1;i<=d;i++){
scanf("%d",&lft[i]);
vis[lft[i]]=1;
}
for(i=1;i<=n;i++){
if(!vis[i]) edge[p[i]].push_back(c[i]);
}
for(i=d,mex=-1,dfn=1;i>=1;i–){
while(dfs(mex+1)){mex++;dfn++;}
ans[i]=mex+1;
dfn++;
edge[p[lft[i]]].push_back(c[lft[i]]);
}
for(i=1;i<=d;i++){
printf("%d\n",ans[i]);
}
return 0;
}

Codeforces Round #547 div.3

F2.Same Sum Blocks (Hard)

【题目描述】

题目描述
有n个数,求出不相交的连续子串和相同的个数最多的区间

【思路】

这题玩的一手好stl
先贴上几个关于STL的博客
map的用法
下标访问负数的方法
//map的下标类型真的是开了bug一样
二维map
//map和vector的搭配真的是绝了
pair的用法
set的用法详解
auto的用法详解
//不过auto好像在dev上总是编译不了不知道为什么

G.Privatization of Roads in Treeland

【题目描述】

题目链接
有一个有n个结点的树(即n-1条边),用最少的染料给树的边染色,连向同一个结点的边染重复颜色则为“bad”树,bad树最多不超过k个,输出最少染料数以及每条边染的颜色

【思路】

从n范围看出这题大概是nlgn的算法吧,统计每个节点的度,坏树一定是度最多的那几个,然后二分一下答案。最后用bfs苒一下色,bfs的部分我开了一个ab数组,也就是向外连接的当前起始点与它的父节点连边的染色,然后从这个颜色开始循环
不过很多题解用dfs做,用当前点当前连边数+1作为染色值赋给新加的边
一开始脑子抽了搞了个拓扑排序,后来发现这个是无向图啊,而且就算每次全部判断一下也不会耗费多长时间,果然是div3的难度

#include <bits/stdc++.h>
using namespace std;
const int MAX=200000;
vector<int> edge[MAX+5];
vector<int> ans;
vector<pair<int,int> >od;
map<pair<int,int>,int> mp;
pair<int,int> p;
queue<int> s;
int ansn,amax,n,k,in[MAX+5],vis[MAX+5],ab[MAX+5],cnt;
/*void topo(){
 int i,t;
 for(i=1;i<=n;i++){
  if(in[i]==1) s.push(i);
 }
 while(!s.empty()){
  t=s.front();
  vis[t]=1;
  s.pop();
  ans.push_back(t);
  for(i=0;i<edge[t].size();i++){
   if(vis[edge[t][i]]) continue;
   in[edge[t][i]]--;
   if(in[edge[t][i]]==1) s.push(edge[t][i]);
  }
 }
}*/
bool pd(int m){
    int i,num=0;
 for(i=1;i<=n;i++){
  if(in[i]>m) num++;
 }
 if(num>k) return true;
 return false;
}
int bi_search(){
 int ll,rr,mm;
 ll=1;
 rr=amax;
 while(ll<rr){
  mm=(ll+rr)/2;
  if(!pd(mm)){
   rr=mm;
  }
  else{
   ll=mm+1;
  }
 }
    return rr;
}
void bfs(int st){
 memset(vis,0,sizeof(vis));
 while(!s.empty()){
  s.pop();
 }
 s.push(st);
 vis[st]=1;
 int u,v,i;
 while(!s.empty()){
  u=s.front();
  s.pop();
  cnt=ab[u];
  for(i=0;i<edge[u].size();i++){
   v=edge[u][i];
   if(!vis[v]){
    vis[v]=1;
       cnt++;
       if(cnt>ansn) cnt=1;
    p.first=u;
       p.second=v;
       mp[p]=cnt;
       p.first=v;
       p.second=u;
       mp[p]=cnt;
       ab[v]=cnt;
       s.push(v);
      }
  }
 }
}
void print(){
 printf("%d\n",ansn);
 for(int i=0;i<od.size();i++){
  printf("%d ",mp[od[i]]);
 }
}
int main(){
 freopen("G.in","r",stdin);
 freopen("G.out","w",stdout);
 int i,u,v;
 scanf("%d%d",&n,&k);
 for(i=1;i<n;i++){
  scanf("%d%d",&u,&v);
  od.push_back(make_pair(u,v));
     edge[u].push_back(v);
     edge[v].push_back(u);
 }
 for(i=1;i<=n;i++){
  in[i]=edge[i].size();
  if(in[i]>amax) amax=in[i];
 }
 //topo();
 ansn=bi_search();
 bfs(1);
 print();
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值