A Rooms and Passages
题意
给n个数,从起点出发,一直往右走,遇到一个前面出现过其相反数的正数就停下,问对于每个起点都能走多少步。
分析
- 倒着递推,如果起点是正数,那么肯定可以走,
ans[i]=ans[i+1]+1
。 - 如果起点是负数,那就得看这个负数对应绝对值在后面出现的最靠前的位置,而且还要看后面的负数所对应的绝对值的最靠前位置,所以这个lst应该是全局更新的。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+50;
int a[N],n,pos[N],ans[N];
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int lst=n+1;
for(int i=n;i>=1;i--){
if(a[i]>0){
ans[i]=ans[i+1]+1;
pos[a[i]]=i;
}else{
if(!pos[-a[i]]){
ans[i]=ans[i+1]+1;
}else{
lst=min(lst,pos[-a[i]]-1);
ans[i]=lst-i+1;
}
}
}
for(int i=1;i<=n;i++){
printf("%d%c",ans[i],i==n?'
':' ');
}
return 0;
}
B Rearrange Columns
题意
有两行字符串,有#
和.
,可以对列进行重排,使得#
连在一起。
分析
- 计算两个
#
,一个#
在上面和在下面的,和没有#
的,如果没有两个#
而且#
有上有下的,就不行。 - 否则,把两个
#
的放中间即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+55;
char a[2][N];
int main(){
scanf("%s",a[0]);
scanf("%s",a[1]);
int n=strlen(a[0]);
int shang=0;
int xia=0;
int mei=0;
int liang=0;
for(int i=0;i<n;i++){
if(a[0][i]=='#' && a[1][i]=='#'){
liang++;
}else if(a[0][i]=='#' && a[1][i]=='.'){
shang++;
}else if(a[0][i]=='.' && a[1][i]=='#'){
xia++;
}else{
mei++;
}
}
if(liang || (shang &&!xia) || (xia&&!shang)){
printf("YES
");
for(int i=0;i<mei;i++){
printf(".");
}
for(int i=0;i<shang+liang;i++){
printf("#");
}
for(int i=0;i<xia;i++){
printf(".");
}
printf("
");
for(int i=0;i<mei;i++){
printf(".");
}
for(int i=0;i<shang;i++){
printf(".");
}
for(int i=0;i<liang+xia;i++){
printf("#");
}
printf("
");
}else{
printf("NO
");
}
return 0;
}
C Jumps on a Circle
题意
一个环标记为0到p-1,从0开始每次跳1,2,3...步,问跳n步之后会有多少个位置访问过。
分析
跳min(n,2p)次之后肯定会回到原点然后重新跳1,2,3...,所以模拟即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+50;
ll n,p;
int vis[N];
int main(){
scanf("%lld%lld",&p,&n);
vis[0]=1;
ll now=0;
for(ll i=1;i<=min(n,2*p);i++){
now=(now+i)%p;
vis[now]=1;
}
int ans=0;
for(ll i=0;i<p;i++){
ans+=vis[i];
}
printf("%d
",ans);
return 0;
}
D Country Division
题意
给一棵树,每次询问给一些点染成红色,一些点染成蓝色,问能不能删掉一些边使得同色之间能相互到达,不同色之间不能相互到达。
分析
- 因为是一棵树,如果可以删的话肯定删一条边就够了。
- 假设1为根,先求红色点的lca,再求蓝色点的lca,如果两个lca不在同一棵子树,那么删掉其中一个连向父节点的边即可。
- 如果两个lca有祖先关系,比如红色lca和蓝色lca的lca就是红色lca,那么如果红色点都在红色lca指向蓝色lca的另一条边上,那就可以,否则,说明有红色点在蓝色点的子树下。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
struct Edge{
int v,next;
}e[N*2];
int d[N],fa[N][20],pw[20];
int cnt,head[N];
void init(){
pw[0]=1;
for(int i=1;i<=20;i++){
pw[i]=pw[i-1]*2;
}
cnt=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v){
e[cnt]=Edge{v,head[u]};
head[u]=cnt++;
e[cnt]=Edge{u,head[v]};
head[v]=cnt++;
}
void dfs(int u){
for(int i=1;pw[i]<=d[u];i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i!=-1;i=e[i].next){
int v=e[i].v;
if(v==fa[u][0]){
continue;
}
fa[v][0]=u;
d[v]=d[u]+1;
dfs(v);
}
}
int lca(int x,int y){
if(d[x]<d[y]){
swap(x,y);
}
int tmp=d[x]-d[y];
for(int i=0;pw[i]<=tmp;i++){
if(tmp&pw[i]){
x=fa[x][i];
}
}
if(x==y){
return x;
}
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int n,q,u,v,ri,bi,x,red[N],blue[N];
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
init();
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
add(u,v);
}
dfs(1);
scanf("%d",&q);
while(q--){
scanf("%d%d",&ri,&bi);
scanf("%d",&red[1]);
int rlc=red[1];
for(int i=2;i<=ri;i++){
scanf("%d",&red[i]);
rlc=lca(rlc,red[i]);
}
scanf("%d",&blue[1]);
int blc=blue[1];
for(int i=2;i<=bi;i++){
scanf("%d",&blue[i]);
blc=lca(blc,blue[i]);
}
int lc=lca(rlc,blc);
if(rlc!=lc && blc!=lc){
printf("YES
");
}else if(rlc==lc){
bool flag=false;
for(int i=1;i<=ri;i++){
if(lca(red[i],blc)==blc){
printf("NO
");
flag=true;
break;
}
}
if(!flag){
printf("YES
");
}
}else if(blc==lc){
bool flag=false;
for(int i=1;i<=bi;i++){
if(lca(blue[i],rlc)==rlc){
printf("NO
");
flag=true;
break;
}
}
if(!flag){
printf("YES
");
}
}
}
return 0;
}
E Third-Party Software - 2
题意
求最少的线段覆盖整个区间。
分析
贪心经典题。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
struct node{
int l,r,id;
bool operator <(const node& rhs)const{
if(l==rhs.l){
return r>rhs.r;
}else{
return l<rhs.l;
}
}
}a[N];
int n,m;
vector<int> res;
int main(){
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
}
sort(a+1,a+1+n);
int now=1,i=1,ans=0;
while(i<=n && now<=m){
int mx=0;
int k=0;
while(i<=n && a[i].l<=now){
if(a[i].r>mx){
mx=a[i].r;
k=i;
}
i++;
}
if(mx<=now-1){
ans=-1;
break;
}
res.push_back(a[k].id);
now=mx+1;
ans++;
}
if(now<=m){
ans=-1;
}
if(ans==-1){
printf("NO
");
}else{
printf("YES
");
printf("%d
",ans);
for(int i=0;i<ans;i++){
printf("%d%c",res[i],i==ans-1?'
':' ');
}
}
return 0;
}
F Friendly Fire
题意
n个怪兽,有两个属性a和b,如果属性a大于另一个怪兽的属性b,就可以打死该怪兽,现在要选出两个怪兽同时打,可能同时死,要使死的怪兽的属性a总和最大。
分析
- 参考了entry上一个外国朋友的思路https://codeforces.com/blog/entry/67178
- 考虑分别对a和对b递增排序,得到两个序列,然后枚举第一个序列的怪兽,然后把第二个序列中当前怪兽能打败的怪兽的a值加入优先队列中,因为这些怪兽肯定能被打死,所以直接取出最大的a值,然后再看取出的怪兽能否打败枚举的怪兽,再考虑是否再更新答案。
- 如果取出的怪兽是本身,就取下一个,注意空队列的判断,取出的怪兽如果没用到,continue之前也要重新入队。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+50;
int n;
struct node{
int a,b,id;
}a[N],b[N];
bool cmp1(node a,node b){
if(a.a==b.a){
return a.b<b.b;
}else{
return a.a<b.a;
}
}
bool cmp2(node a,node b){
if(a.b==b.b){
return a.a<b.a;
}else{
return a.b<b.b;
}
}
priority_queue<pair<int,int> > q;
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].a,&a[i].b);
a[i].id=i;
b[i]=a[i];
}
sort(a+1,a+1+n,cmp1);
sort(b+1,b+1+n,cmp2);
int ans=0;
int le=0,ri=0;
bool up=false;
for(int i=1,j=1;i<=n;i++){
while(j<=n && b[j].b<=a[i].a){
q.push({b[j].a,j});
j++;
}
if(q.empty()){
continue;
}
int mx=q.top().first;
int idx=q.top().second;
// printf("%d %d %d
",i,a[i].a,a[i].b);
// printf("%d %d %d %d
",idx,mx,b[idx].id,a[i].id);
if(b[idx].id==a[i].id){
// printf("%d F
",i);
auto tmp=q.top();
q.pop();
if(q.empty()){
q.push(tmp);
continue;
}
mx=q.top().first;
idx=q.top().second;
q.push(tmp);
}
int t=mx+(b[idx].a>=a[i].b?a[i].a:0);
if(t>ans){
up=true;
ans=t;
le=a[i].id;
ri=b[idx].id;
}
}
printf("%d
",ans);
if(!up){
printf("1 2
");
}else{
printf("%d %d
",le,ri);
}
return 0;
}
H Missing Number
题意
有0-n一共n+1个数,现在删去一个数,通过交互得到删去的数。
交互是询问第x个数的第i位是0还是1。
分析
-
先分别询问每个数的最低位,统计1的个数,显然如果不缺数的话,1的个数应该是(frac{n+1}{2}),可以由此判断缺的数这一位是0还是1。
-
然后可以排除掉一半不符合的数,继续询问第2位。
-
用set存数字下标(即第几个数)比较好写。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
set<int> ns,t;
int main(){
int n;
cin >> n;
for(int i=1;i<=n;i++){
ns.insert(i);
}
//从最低位开始询问
int i=0;
int res;
int ans=0;
while(ns.size()>=1){
int sz=ns.size();
t.clear();
int b=0;
for(auto it=ns.begin();it!=ns.end();it++){
int x=*it;
//询问第x个数的第i位
cout << "? " << x << " " << i << endl;
cout.flush();
cin >> res;
if(res){
t.insert(x);
b++;
}
}
if(b<(sz+1)/2){
//缺少的数第i位为1
ans+=(1<<i);
ns=t;
}else{
for(auto it=t.begin();it!=t.end();it++){
ns.erase(*it);
}
}
i++;
}
cout << "! " << ans << endl;
return 0;
}
I Painting a Square
题意
一个边长为b的小正方形给一个边长为a的大正方形涂色,问最小需要走的步数。
分析
- 沿着边缘螺旋走进去是最优的。
- 所以先沿这边缘走两个边长,就转化为a-b的小正方形的情况了。
- 本地跑不出来所以手动栈模拟,不过评测机上能跑出来。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b;
ll fun(ll a,ll b){
// printf("%lld %lld
",a,b);
if(a-b<b){
return 3ll*(a-b);
}else{
return fun(a-b,b)+2ll*(a-b)+b;
}
}
stack<ll>st;
ll solve(ll a,ll b){
ll aa=a;
while(aa-b>=b)
{
st.push(aa);
aa-=b;
}
ll ans=3ll*(aa-b);
while(!st.empty())
{
ans=ans+2ll*(st.top()-b)+b;
st.pop();
}
return ans;
}
int main(){
scanf("%lld%lld",&a,&b);
// ll ans=solve(a,b);
ll ans=fun(a,b);
printf("%lld
",ans);
return 0;
}
J The Power of the Dark Side - 2
题意
n个人,每人有三种属性,如果有两种属性严格大于的话就能击败另一个人,问对于每个人,如果在每场战斗之前都能随意调整属性值,最多能击败多少人。
分析
- 考虑每个人能被击败的条件,显然就是对方属性值总和至少有(两个最小的属性值+2)。
- 所以把每个人能被击败的下限求出来,排序,然后枚举每个人的属性总和,upper_bound()查出能击败的人数,再判断这些人里面是否有自身。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+55;
ll a[N][3];
ll sum[N];
int n;
vector<ll> v;
vector<int> ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld",&a[i][0],&a[i][1],&a[i][2]);
sum[i]=a[i][0]+a[i][1]+a[i][2];
sort(a[i],a[i]+3);
v.push_back(a[i][0]+a[i][1]+2);
}
sort(v.begin(),v.end());
for(int i=1;i<=n;i++){
int k=upper_bound(v.begin(),v.end(),sum[i])-v.begin();
ll t=sum[i]-(a[i][0]+a[i][1]+2);
if(t>=0){
k--;
}
ans.push_back(k);
}
for(int i=0;i<n;i++){
printf("%d%c",ans[i],i==n-1?'
':' ');
}
return 0;
}
K Deck Sorting
题意
分析后的题意是给一个只含RGB的字符串,从中抽出一个子序列,和剩下的子序列拼在一起,使得拼接后的字符串相同字符在一起。
分析
- 枚举6个全排列,也就是拼接后的字符串情况,假设枚举RBG,那么就得把所有R取出。所有G剩下,要考虑的就是B,如果B是在最后一个R的后面,那就是跟随R取出,如果是在第一个G前面,那就跟随G留下,否则,即如果B在最后一个R前面且再第一个G后面,那就是不合法的。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+55;
char s[N];
set<char> lt;
char idx[]={'R','G','B'};
int main(){
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++){
lt.insert(s[i]);
}
int siz=lt.size();
if(siz<3){
printf("YES
");
}else{
for(int i=0;i<3;i++){
char le=idx[i];
for(int j=0;j<3;j++){
if(i==j){
continue;
}
char ri=idx[j];
char md=idx[3-i-j];
int leri=-1;
int rile=-1;
for(int k=n;k>=1;k--){
if(s[k]==le){
leri=k;
break;
}
}
for(int k=1;k<=n;k++){
if(s[k]==ri){
rile=k;
break;
}
}
bool flag=true;
for(int k=1;k<=n;k++){
if(s[k]==md){
if(k<leri && k>rile){
flag=false;
break;
}
}
}
if(flag){
printf("YES
");
return 0;
}
}
}
printf("NO
");
}
return 0;
}
M Shlakoblock is live!
题意
n个东西有p属性和v属性,现在可以选择将一些东西的v属性加1,求最大的每个东西的p的期望。
分析
p越大,v加1对期望的贡献肯定越大,所以对p排序,然后对v加1,更新答案,直到对答案没有影响就退出。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1050;
int t,n;
struct node{
ll zi,mu;
};
bool cmp(node a,node b){
return a.zi*b.mu>a.mu*b.zi;
}
struct info{
int p,v,id;
bool operator <(const info& rhs)const{
return p>rhs.p;
}
}a[N];
vector<int> ans;
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d",&n);
ll zi=0,mu=0;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i].p,&a[i].v);
zi+=a[i].p*a[i].v;
mu+=a[i].v;
a[i].id=i;
}
sort(a+1,a+1+n);
ans.clear();
node tt{zi,mu};
for(int i=1;i<=n;i++){
zi+=a[i].p;
mu++;
node tmp={zi,mu};
if(cmp(tmp,tt)){
ans.push_back(a[i].id);
tt=tmp;
}else{
break;
}
}
ll g=__gcd(tt.zi,tt.mu);
printf("%lld/%lld
",tt.zi/g,tt.mu/g);
int siz=ans.size();
sort(ans.begin(),ans.end());
printf("%d
",siz);
if(!siz){
printf("
");
}
for(int i=0;i<siz;i++){
printf("%d%c",ans[i],i==siz-1?'
':' ');
}
}
return 0;
}