2019, XII Samara Regional Intercollegiate Programming Contest

A. Rooms and Passages

在这里插入图片描述
题目描述:把一个序列分为两类,ai>0和ai<0, 如果在 i 点门口需要第一类通行证且持有第一类通行证可以直接从i-1点走向 i 点 且无其他影响,如果 i 点门口需要第二类通行证可以从i-1点走向i点,并使 | ai | 这个通行证失效。假设一开始处于 i 点,并拥有所有种类的通行证(种类指 | ai | ) ,问从i 点走向n 点最多能走几步?
题目一开始没读懂,不过这道题总感觉读起来别扭。
这道题比较好想的思路就是从前往后遍历,遇到的负数存其相反数,下一次遇到存过的数就直接break然后输出计数。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int N = 500010;
int a[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {   map<int,int> mp;
        int cnt=0;
        if(a[i]<0) mp[-a[i]]=1;
        cnt++;
        for(int j=i+1;j<=n;j++)
        {
            if(mp[a[j]]==1) break;
            if(a[j]<0) mp[-a[j]]=1;
            cnt++;
        }
        cout<<cnt<<" ";
    }
}

在这里插入图片描述
然而这个n的范围卡掉了暴力,直接做会超时。
那么这道题一定是有一些性质:
在这里插入图片描述
这一组序列中,因为 -3,所以-3前边的数都最远遍历到 3。所以显然-3之前的数是具有单调性的,所以我们就可以利用双指针的思想,让 i 遍历序列的每一个数,j 从1~n遍历。当 j 遍历到 3 时,让-3之前的数赋值为区间长:i - j,然后到-3解除标记,继续向后遍历。这样时间复杂度就比n^2快很多了。
AC代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int N = 500010;
int a[N];
int ans[N];
map<int,int> mp;
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    int j=1;
    for(int i=1;i<=n;i++)
    {   
        while(j<=n)
        {
            if(a[j]>0&&mp[a[j]]>=1)
            {
                break;
            }
            if(a[j]<0) mp[-a[j]]++;
            j++;
        }
        ans[i]=j-i;
        if(a[i]<0) mp[-a[i]]--;
    }
    for(int i=1;i<=n;i++)
    {
        cout<<ans[i]<<" ";
    }
}

B. Rearrange Columns

在这里插入图片描述
题意:给定a,b两个字符串,每一列都可以互换,最终使得 # 构成连通块,如果可以输出YES并输出任意一种连通块方案,否则输出NO。
借鉴csdn大佬思路:
在这里插入图片描述

因为只有四种列形式,所以可以对每一类计数,如果没有上下同#但有上.下#和下.上#则一定不可以连通,而如果有上下同#,最终把它们按图3的形式排列则一定可以连通。
这种整体计数的方法非常妙。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1005;
char s[2][maxn];
int cnt[4];
int main(){
    scanf("%s%s",s[0],s[1]);
    int len=(int)strlen(s[0]);
    for(int i=0;i<len;i++){
        if(s[0][i]=='#'&&s[1][i]=='.') cnt[0]++;
        else if(s[0][i]=='#'&&s[1][i]=='#') cnt[1]++;
        else if(s[0][i]=='.'&&s[1][i]=='#') cnt[2]++;
        else if(s[0][i]=='.'&&s[1][i]=='.') cnt[3]++;
    }
    if(cnt[0]&&cnt[2]&&!cnt[1]) printf("NO\n");
    else{
        printf("YES\n");
        for(int j=0;j<cnt[0];j++) printf("#");
        for(int j=0;j<cnt[1];j++) printf("#");
        for(int j=0;j<cnt[2];j++) printf(".");
        for(int j=0;j<cnt[3];j++) printf(".");
        printf("\n");
        for(int j=0;j<cnt[0];j++) printf(".");
        for(int j=0;j<cnt[1];j++) printf("#");
        for(int j=0;j<cnt[2];j++) printf("#");
        for(int j=0;j<cnt[3];j++) printf(".");
        printf("\n");
    }
    return 0;
}
//csdn大佬代码,不自己写了

C. Jumps on a Circle

题意:
一个环,从0~n-1.
每一次走比前一次走的多一步,如1,2,3…。如果走到n-1下一步走回0号点。
给n个点 m次操作,问有多少点被走到。
思路:这种题先找循环节,这个循环节并不是很显然易见。
试几个样例,发现奇数n,循环n次出现循环,偶数n循环2*n次出现循环,按这个思路可以猜出答案。
在这里插入图片描述

我们需要证明走m步和走一个循环节的步数等价:
假设存在一个循环节 n ,那么当n为偶数的时候此式不一定成立,但不论n为奇数偶数,2n一定成立, 所以直接把2n当做最小次数解即可。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#include<set>
#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e7+10;
int vis[N];
int main()
{
	LL n,m;
	cin>>n>>m;
	LL y=min(m,2*n);
	LL res=0;
	LL x=0;
	for(int i=0;i<=y;i++)
	{
	    x+=i;
	    x%=n;
	    if(!vis[x])
	    {
	        res++;
	        vis[x]=1;
	    }
	}
	cout<<res<<endl;
	
}

I. Painting a Square

在这里插入图片描述
题意:给定一个白色大正方形和红色小正方形,要用小正方形整体做一个刷子,刷满大正方形,求最小步数。
在这里插入图片描述
显然贴正方形向里走最小,每次大正方形的L变成变成了L-2*l,要考虑每一次进入下一个状态时要加上的一个 l ,而这个 l 也有可能大于 L,此时要减去上一次的 l ,取而代之的是加上本次进入dfs的L,因为l比L大了,所以直接return -l + L 即可,不会再转圈了。

#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e7+10;
long long int dfs(long long int L,long long int l)
{   if(l>L) return -l+L;//减掉上一层过来的一个l,实际长度应该是L
    if(L-2*l>0) return 4*(L-l)+dfs(L-2*l,l);//贴最外层转一周+进入下一次的 l 
    else return 3*(L-l); //最后一个正方形直接刷完即可
}
int main()
{
	long long int L,l;
	cin>>L>>l;
	cout<<dfs(L,l);
}

L. Inscribed Circle

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int main()
{
    double x1,y1,r1,x2,y2,r2;
    scanf("%lf%lf%lf%lf%lf%lf",&x1,&y1,&r1,&x2,&y2,&r2);
    if(x1>x2) swap(x1,x2),swap(y1,y2),swap(r1,r2);
    double L=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    double r=(r1+r2-L)/2;
    double x=(L-r2+r)/L*(x2-x1)+x1;
    double y=(L-r2+r)/L*(y2-y1)+y1;
    printf("%.10f %.10f %.10f",x,y,r);
    return 0;
}

E. Third-Party Software - 2

在这里插入图片描述
给出两个数n和m。
要求用1~n 个线段覆盖[1,m]这个区间。
经典区间覆盖问题。
在这里插入图片描述
按左端点排序,每一次找在基准之下,右端点最靠右的一个区间。
基准之下的区间就是左端点小于已选区间的右端点的区间。
选中区间之后,更新基准为当前区间的右端点。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
int cnt[N];
int t=0;
struct node
{
    int l;
    int r;
    int num;
}a[N];
bool cmp(node a,node b)
{   
    return a.l<b.l;
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].l>>a[i].r;
        a[i].num=i;
    }
    sort(a+1,a+1+n,cmp);
    int st=0;//最开始的基准是待覆盖的区间左端点
    int ed=m;//终点
    for(int i=1;i<=n;i++)
    {
        int j=i,r=-1;
        int k=i;//找在基准之下,右端点最靠右的一个区间
        while(j<=n&&a[j].l<=st+1)
        {
            if(a[j].r>r) 
            {
                r=a[j].r;
                k=j;
            }
            j++;
        }
        if(r<st)//无法继续向后覆盖
        {
            cout<<"NO"<<endl;
            return 0;
        }
        cnt[++t]=k;
        if(r>=ed)//如果可以覆盖,r>=ed一定是出口
        {
            cout<<"YES"<<endl;
            cout<<t<<endl;
            for(int i=1;i<=t;i++)
            {   if(i!=t)
                cout<<a[cnt[i]].num<<" ";
                else cout<<a[cnt[i]].num;
            }
            return 0;
        }
        st=r;
        i=j-1;
    }
    cout<<"NO"<<endl;//说明最后一次循环没有覆盖成功
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值