7.18 2020牛客暑期多校训练营(第三场)题解及补题

7.18 2020牛客暑期多校训练营(第三场)题解及补题

比赛过程

D题,B题,A题,C题。

D题签到。AB同时开的,B其实可以看成环思路,只不过一开始str+=str,太费时间了,改了这立马过;A题一开始愣是没看懂题意,其实真正需要分析的是情况1;C题最后分析的是长度为9的边和长度为8的边,其实想想就是题面让你看的恶心,就是(用我们的解法)两个带60°角的三角形是正的还是反的。

题解

A Clam and Fish

题意

有一个捕鱼游戏,共有n个池塘,编号1-n,每个池塘有四种状态,分别为0,1,2,3.
0代表:没有鱼也没有蛤
1代表:没有鱼,有一条蛤
2代表:有一条鱼,没有蛤
3代表:有一条鱼和一条蛤
对于每一个池塘,我们有四种选择:
1.如果池塘里有蛤,可以用这个蛤制作一包鱼饵,所拥有的鱼饵包装数将增加一。在此操作之后,您可以使用此包鱼饵捕获鱼。
2.如果池塘里只有一条鱼,则无需任何鱼饵就可以抓到这条鱼。在此操作之后,拥有的鱼饵包数不会改变。
3.如果有至少一包鱼饵。即使在此阶段没有鱼,也可以使用一包鱼饵捕获一条鱼。在此操作之后,拥有的鱼饵包数将减少一。
4.什么也不做

解法

本题为贪心,我们的策略为,如果池塘为1,那么制作鱼饵;如果池塘为0并且已经有了鱼饵,使用鱼饵捕一条鱼;如果池塘为2或者3,
补一条鱼,由于最后鱼饵可能会有剩余的,所以最后答案要加上cnt/2,也就是1号池塘有一半是需要制作鱼饵,另一半是来捕鱼。

代码

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int inf = ~0u >> 1;
typedef pair<int,int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n) - 1; i >= a; --i)
int main() {
    IO;
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        string s;
        cin >> s;
        int cnt = 0;
        int ans = 0;
        REP(i, 0, n) {
            if (s[i] == '1') {
                cnt++;
            }
            else if (s[i] == '0' && cnt > 0) {
                ans++;
                cnt--;
            }
            else if (s[i] == '3' || s[i] == '2') {
                ans++;
            }
        }
        cout << ans + cnt / 2 << endl;
    }
    return 0;
}

B Classical String Problem

题意

给一个由小写字母组成的字符串,以及Q个操作,操作有两种类型,操作M表示修改字符串:给定整数x,需要根据x的值修改S。
如果x为正,则将S中最左边的x个字母移到S的右边;否则,移动最右边的|x|长度到S左侧;操作A表示询问字符:给定正整数x。
输出当前字符串S中的第x个字母是什么。

解法

该题题意比较好理解,但是要注意超时的问题,由于M操作比较费时,我们不可以模拟每一个操作,我们需要记录每一次操作S
字符的移动距离,当询问字符时,只需要O(1)输出对应位置的字符即可,剩下的就是取模的细节了。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int inf = 0x3f3f3f3f;
typedef long long ll;
const int maxn = 2000500;
const ll mod = 1e9 + 7;
typedef pair<int,int> pii;
const double pi = acos(-1);
int main() {
    IO;
    string str;
    cin>>str;
    int T;
    cin>>T;
    int cnt=0;
    int len=str.length();
    while(T--) {
        char ch;
        int x;
        cin>>ch>>x;
        if(ch=='M') {
            if(x>0) {
                cnt+=x;
                cnt%=(len);
            }
            else {
                cnt+=x;
                if(cnt<0) {
                    cnt+=(len);
                }
            }
        }
        else {
            cout<<str[(cnt+x-1)%len]<<endl;
        }   
    }  
    return 0;

}

C Operation Love

题意

t组测试,每次测试按顺时针或逆时针给20个点,是一个手的形状,问是左手还是右手。

解法

这里放的是训练时的比较“野”的一种做法:

image-20200725213435501

题图如上,因为左手和右手是镜面对称的,所以不同点是很容易找到的;另外,给定的点是顺时针或者逆时针给出的,所以要是想根据点的输入判断左手还是右手是不可能的。可以选择凸包什么的,但是当时看到题面里面提到,手的形状是不会放大或者缩小的,但是可以旋转和平移,平移的话,所有的点加或者是减都是一样的形状。

所以选定了(1,0),(10,0),(10,8)相对应的三个点,将(10,0)的坐标置为0,判断其他两个点的象限即可。

方法比较“野”,但是不失为一种方法。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int inf = 0x3f3f3f3f;
typedef long long ll;
const int maxn = 2000500;
const ll mod = 1e9 + 7;
typedef pair<int,int> pii;
const double pi = acos(-1);
struct node
{
    double x,y;
}a[25],b,c,d;
double cal(node a,node b) {
    return sqrt((a.x-b.x)*((a.x-b.x))+(a.y-b.y)*(a.y-b.y));
}
int main() {
    IO;
    int T;
    cin>>T;
    while(T--) {
        cin>>a[1].x>>a[1].y;
        for(int i=2;i<=20;i++) {
            cin>>a[i].x>>a[i].y;
        }
        bool flag=true;
        a[0].x=a[20].x;
        a[0].y=a[20].y;
        a[21].x=a[1].x;
        a[21].y=a[1].y;
        for(int i=1;i<=20;i++) {
            if(cal(a[i],a[i-1])>9.00-1e-1&&cal(a[i],a[i-1])<9.00+1e-1 && cal(a[i],a[i+1])>8.00-1e-1&&cal(a[i],a[i+1])<8.00+1e-1) {
                c.x=a[i].x;
                c.y=a[i].y;
                b.x=a[i+1].x-c.x;
                b.y=a[i+1].y-c.y;
                d.x=a[i-1].x-c.x;
                d.y=a[i-1].y-c.y;
                flag=false;
                break;
            }
            else if(cal(a[i],a[i+1])>9.00-1e-1&&cal(a[i],a[i+1])<9.00+1e-1 && cal(a[i],a[i-1])>8.00-1e-1&&cal(a[i],a[i-1])<8.00+1e-1) {
                c.x=a[i].x;
                c.y=a[i].y;
                b.x=a[i-1].x-c.x;
                b.y=a[i-1].y-c.y;
                d.x=a[i+1].x-c.x;
                d.y=a[i+1].y-c.y;
                flag=false;
                break;
            }
        }
        if(b.y==0) {
            if(b.x>0&&d.y>0) cout<<"right\n";
            else if(b.x>0&&d.y>0) cout<<"left\n";
            else if(b.x<0&&d.y>0) cout<<"left\n";
            else cout<<"right\n";
        }
        else if(b.x==0) {
            if(b.y>0&&d.x>0) cout<<"left\n";
            else if(b.y<0&&d.x>0) cout<<"right\n";
            else if(b.y>0&&d.x<0) cout<<"right\n";
            else cout<<"left\n";
        }
        else if(b.x>0&&b.y>0) {
            if(d.x>0) cout<<"left\n";
            else cout<<"right\n";
        }
        else if(b.x<0&&b.y>0) {
            if(d.x>0) cout<<"left\n";
            else cout<<"right\n";
        }
        else if(b.x<0&&b.y<0) {
            if(d.x<0) cout<<"left\n";
            else cout<<"right\n";
        }
        else {
            if(d.x<0) cout<<"left\n";
            else cout<<"right\n";
        }

    }
     
    return 0;

}

D Points Construction Problem

题意

在2D平面内,每个格点(整数点)有一个白点,可以将其中一些点涂黑。问能否将n个白点涂黑,使得有m对相邻的白点和黑点(指哈夫曼距离为1)

解法

借鉴一位大佬的比赛时的思路,感觉好理解

①若每个黑点都是不相邻的,则可以发现涂黑n个的黑白对数上限为\(4*n\)

②如果每个黑点相邻,并排成一行,则此时黑白对数为\(2*n+2\)

③可以发现黑白点对数一定是偶数,在②的基础上,将链上的黑点逐个拿出并放置在互不相邻处,则可以实现\(2∗n+2\)至$ 4∗n$所有偶数m的构造。

④如果m比\(2∗n+2\)还要小,可以在②的基础上,将这条黑点链逐步卷曲来减少黑白点对数,每次减2

代码

#include <bits/stdc++.h>
using namespace std;
int vis[100][100];
int cal(int u,int v){//周围黑点的个数
    return vis[u-1][v]+vis[u+1][v]+vis[u][v+1]+vis[u][v-1];
}
int main () {
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        if(n*4<m || m%2==1){
            puts("No");
            continue;
        }
        int srt=sqrt(n);
        int rest=n-srt*srt;
        int down=srt*4;
        if(rest>0){
            down+=2;
        }
        if(rest>srt){
            down+=2;
        }
        if(m<down){
            puts("No");
            continue;
        }
        puts("Yes");
        if((n*4-m)/2<n){//单链
            int num2=(n*4-m)/2;
            int num4=n-num2;
            for(int i=1;i<=num2+1;i++){
                printf("%d %d\n",i,1);
            }
            num4--;
            for(int i=1;i<=num4;i++){
                printf("%d %d\n",-i,-i);
            }
        }
        else{//进行卷
            memset(vis,0,sizeof vis);
            int num0=(2*n+2-m)/2;
            int cnt=0;
            vis[50][50]=1;
            cnt++;
            printf("50 50\n");
            int x=50,y=50;
            int turn=0;//0右,1上,2左,3下
            for(int step=1;step<=10;step++){
                for(int sstep=1;sstep<=2;sstep++){//同一步长走两次
                    if(turn==0){
                        for(int i=1;i<=step;i++){
                            x++;
                            vis[x][y]=1;
                            cnt++;
                            printf("%d %d\n",x,y);
                            if(cal(x,y)==2){
                                num0--;
                            }
                            if(num0==0 ||cnt==n){
                                break;
                            }
                        }
                        if(num0==0||cnt==n){
                            break;
                        }
                        turn=(turn+1)%4;
                    }
                    else if(turn==1){
                        for(int i=1;i<=step;i++){
                            y++;
                            vis[x][y]=1;
                            cnt++;
                            printf("%d %d\n",x,y);
                            if(cal(x,y)==2){
                                num0--;
                            }
                            if(num0==0 ||cnt==n){
                                break;
                            }
                        }
                        if(num0==0||cnt==n){
                            break;
                        }
                        turn=(turn+1)%4;
                    }
                    else if(turn==2){
                        for(int i=1;i<=step;i++){
                            x--;
                            vis[x][y]=1;
                            cnt++;
                            printf("%d %d\n",x,y);
                            if(cal(x,y)==2){
                                num0--;
                            }
                            if(num0==0 ||cnt==n){
                                break;
                            }
                        }
                        if(num0==0||cnt==n){
                            break;
                        }
                        turn=(turn+1)%4;
                    }
                    else if(turn==3){
                        for(int i=1;i<=step;i++){
                            y--;
                            vis[x][y]=1;
                            cnt++;
                            printf("%d %d\n",x,y);
                            if(cal(x,y)==2){
                                num0--;
                            }
                            if(num0==0 ||cnt==n){
                                break;
                            }
                        }
                        if(num0==0||cnt==n){
                            break;
                        }
                        turn=(turn+1)%4;
                    }
                }
                if(num0==0||cnt==n){
                    break;
                }
            }
            if(cnt!=n){
                if(turn==0){//右转下
                    while(cnt<n){
                        y--;
                        printf("%d %d\n",x,y);
                        cnt++;
                    }
                }
                else if(turn==1){//上转右
                    while(cnt<n){
                        x++;
                        printf("%d %d\n",x,y);
                        cnt++;
                    }
                }
                else if(turn==2){//左转上
                    while(cnt<n){
                        y++;
                        printf("%d %d\n",x,y);
                        cnt++;
                    }
                }
                else if(turn==3){//下转左
                    while(cnt<n){
                        x--;
                        printf("%d %d\n",x,y);
                        cnt++;
                    }
                }
            }
        }
    }
}

E Two Matchings

题意

给你一个数列,从a1−>an,你需要找到两个匹配序列p,q其中\(pi≠qi\)
使得(∑(n i=1)abs(ai−api))/2+(∑(n i=1)abs(ai−aqi))/2最小,问这个最小值是多少。n保证为偶数,sum(n)<=2e5
匹配序列的定义为Pi!=i并且对于所有的Ppi=i

解法

第一个序列P是可以确定的最小值,也就是对a数组排序后将每相邻的两个相互连接,序列Q使用DP维护最小值,至于如何DP,
我们可以观察题目得出所有长度大于8的序列,都可以分解为长度为4或者长度为6的序列,剩下的就是贪心计算最小边权之和。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+100;
int a[N];
ll dp[N];
int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",a+i);
		sort(a+1,a+1+n);
		dp[0]=0;
		dp[2]=inf;
		dp[4]=a[4]-a[1];
		for(int i=6;i<=n;i+=2)
			dp[i]=min(dp[i-4]+a[i]-a[i-3],dp[i-6]+a[i]-a[i-5]);
		printf("%lld\n",2*dp[n]);
	}
    return 0;
}

F Fraction Construction Problem

题意

给两个正整数a和b,要求找到四个正整数c,d,e,f,满足\(\frac{a}{b}=\frac{c}{d}-\frac{e}{f}\)

解法

此题看到数学公式想到去构造,想怎么构造出来这个式子

①如果分子分母存在公共因子,b不是质数,构造\(\frac{a}{b}=\frac{a+x}{b}-\frac{x}{b}\),然后我们对分子分母同除以a,b的一个公因子,我们设为g,那么就会有\(\frac{a}{b}=\frac{\frac{a+x}{g}}{\frac{b}{g}}-\frac{\frac{x}{g}}{\frac{b}{g}}\),由于x是任意的,x=g,所以\(\frac{a}{b}=\frac{\frac{a}{g}+1}{\frac{b}{g}}-\frac{1}{\frac{b}{g}}\)

②如果分子分母最简化了,分母的质因子不超过一个,比如8,16,那么无解。

③分子分母最简化,分母的质因子超过一个,有\(df=b\)\(gcd(d,f)=1\)。那么只要找到d,f,然后令\(cf-ed=a\),也就是\(fx-dy=gcd(f,d)*a\)。熟悉的配料,扩展欧几里得。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=2e6+10;

int min_prim[maxn],prim[maxn];
int vis[maxn];

void init()
{
    int m=sqrt(maxn);
    for (int i=2; i<m; i++){
        if (!vis[i]){
            min_prim[i]=i;
            for (int j=i*i; j<maxn; j+=i){
                vis[j]=1;
                if (!min_prim[j]) min_prim[j]=i;
            }
        }
    }
    for (int i=1; i<maxn; i++){
        if (!vis[i]) continue;
        prim[i]++;
        int x=i;
        while (x%min_prim[i]==0) x/=min_prim[i];
        if (x!=1) prim[i]++;
    }
}

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if (!b) {
        x=1; y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

int main()
{
    int t;
    init();
    scanf ("%d",&t);
    while (t--){
        int a,b;
        scanf ("%d%d",&a,&b);
        int g=__gcd(a,b);
        if (g!=1) {
            printf("%d %d %d %d\n",a/g+1,b/g,1,b/g);
            continue;
        }
        if (prim[b]<=1) {printf("-1 -1 -1 -1\n"); continue;}
        ll d=1,f=b;
        while (f%min_prim[b]==0) {
            f/=min_prim[b];
            d*=min_prim[b];
        }
        ll c,e;
        exgcd(f,d,c,e);
        e=-e;
        while (e<0 || c<0) e+=f,c+=d;
        printf ("%lld %lld %lld %lld\n",1LL*c*a,d,1LL*e*a,f);
    }
    return 0;
}

G Operating on a Graph

题意

给你一个由\(\,n\,\)个点\(\,m\,\)条边组成的无向图,起初每个顶点\(\,v_i\,\)属于\(\,i\,\)团。有\(\,q\,\)次查询,每次查询给你一个\(\,o_i\,\),将与\(\,o_i\,\)团相邻的团合并到\(\,o_i\,\)团中。

解法

利用并查集记录每个顶点属于那个团,再用vector存每个团的相邻的点,注意合并团的时候,需要把被合并的团的相邻点也存到当前团的相邻点中。具体操作看代码注释。

代码

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 8e5 + 5;
const int inf = ~0u >> 1;
typedef pair<int, int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n)-1; i >= a; --i)
int f[maxn];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
vector<int> e[maxn];
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        REP(i, 0, n) {
            f[i] = i;
            e[i].clear();
        }
        REP(i, 0, m) {
            int u, v;
            scanf("%d%d", &u, &v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        int q;
        scanf("%d", &q);
        while (q--) {
            int o;
            scanf("%d", &o);
            if (find(o) != o) {  //没有点属于o团,就没有任何操作可做
                continue;
            }
            vector<int> temp = e[o];
            e[o].clear();
            for (auto it : temp) {   //遍历此时与o团相邻的点
                int num = find(it);  //此时相邻点所属的团
                if (num == o)
                    continue;  //如果相邻点已经在o团内,就不用做任何操作
                f[num] = o;  //否则就把这个相邻点合并到o团中
                if(e[o].size() < e[num].size())
                    swap(e[o], e[num]); 
                e[o].insert(e[o].end(), e[num].begin(), e[num].end());//将新合并到o团中的这个点的相邻点也记录到o团的相邻点中
            }
            //REP(i, 0, n) { printf("%d ", find(i)); }
        }
        REP(i, 0, n) { printf("%d ", find(i)); }
        printf("\n");
    }
    return 0;
}

L Problem L is the Only Lovely Problem

题意

签到题。给一个字符串,要求判断是不是以“lovely”为开头,如果是,输出lovely,否则输出ugly。

解法

使用string的find函数即可,注意大小写。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e6+5;
const ll mod =1000000007;
int main(){
	string s;
	while(cin>>s){
		int len=s.length();
		for(int i=0;i<len;i++){
			if(isupper(s[i])){
				s[i]=tolower(s[i]);
			}
		}
		int p;
		if((p=s.find("lovely"))==0){
			cout<<"lovely"<<endl;
		}else{
			cout<<"ugly"<<endl;
		}
	} 
}
posted @ 2020-07-26 14:46  cugbacm03  阅读(137)  评论(0)    收藏  举报