February 18, 2020

2020寒假 SWJTU 校队寒假选拔赛第一场 题解

剪枝剪去我们的疯狂/ SPFA告诉我前途在何方/ 01背包装下了忧伤/ 笑颜 洋溢脸庞

2020寒假 SWJTU 校队寒假选拔赛第一场 题解

A.切割数列

描述

给你一个正整数n.
以及一个整数序列a[1..n].
现在,你需要决定是否要在a[i]和ai+1切割一次,使得这个序列分成若干个序列。
且要求,全部切割完以后,每个序列中,奇数和偶数出现的次数都是一样的.
如{1,2,3,4}可以切割一次变成{1,2}{3,4}两个序列,且每个序列中奇数和偶数出现的次数都是一样的为1.
每切割一次的代价是|a[i]-a[i+1]|.
问你在不超过B代价的情况下,你最多能对这个序列切割几次?

输入

输入的第一行为两个整数n(2<=n<=100),B(1<=B<=100).
第二行n个整数,表示a1..n.
保证初始序列中,奇数和偶数出现的次数都是一样的。

输出

输出只有一个整数,表示最多能对这个序列切割的次数.

思路

首先考虑在什么样的地方可以切,也就是从左往右扫,当扫到某个位置扫过的奇数偶数个数相同,就可以切割,统计出所有可切割的位置之后,再根据贪心,判断能够切几次即可。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100+10;
int a[maxn],b[maxn];
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	int jj=0,od=0,cnt=1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<n;i++){
		if(a[i]%2==0){
			od++;
		}
		else jj++;
		if(jj==od){
			b[cnt]=a[i]-a[i+1];
			if(b[cnt]<0) b[cnt]=b[cnt]*(-1);
			cnt++;
		}
	}
	cnt--;
	sort(b+1,b+cnt+1);
	int ans=0;
	for(int i=1;i<=cnt;i++){
		if(k>=b[i]){
			ans++;
			k-=b[i];
		}
	}
	printf("%d",ans);
	return 0;
}

B.超级号码

描述

最近小A得到了一串号码——一个包含了 n 个数字 a_1 a_2 ... a_n的数列。小A认为一个数列是超级号码,如果它能被分为两个或更多的有相同值的部分。例如,号码350178 是超级号码因为它可以被分为三个部分350, 17 和 8: 3+5+0=1+7=8。每一个数字只能属于一个部分 。
帮小A看看他的号码是不是超级号码。

输入

第一行包含一个整数 n (2 <= n <= 100) — 号码的数字个数
第二行包含 n 个数字 a_1 a_2 ... a_n (0 <= a_i <= 9) — 小A的号码. 数字间没有空格。

输出

如果是超级号码则输出 "YES", 否则输出 "NO" (不带引号)。

思路

枚举分成的每一部分和为多少,判断是否能成立即可。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100+10;
char a[maxn];
int n,sum=0;
int keller(){
    int ke=0,ls=sum/2;
    for(int i=0;i<=ls;i++){
        int s=0;
        if(ke)
			return 1;
        for(int j=0;j<n;j++){
			s+=a[j]-'0';
            if((a[j]-'0')>i||s>i)
				break;
            if(s==i){
                ke=1;s=0;
            }
        }
        if(s!=0){
			ke=0;
		}
    }
    return ke;
}
int main(){
    cin>>n;
    scanf("%s",a);
    for(int i=0;i<n;i++){
        sum+=(a[i]-'0');
    }
    if(keller())
		cout<<"YES";
    else
		cout<<"NO";
	return 0;
}

C.三角形

描述

请你找到三个点(x1,y1),(x2,y2),(x3,y3).
其中0<=x1,x2,x3<=n,0<=y1,y2,y3<=m.
每个点的坐标值都要为整数.
使得这3个点组成的三角形的面积等于n*m/k.

输入

输入只有一行,3个正整数n,m,k(1<=n,m<=10^9,2<=k<=10^9).

输出

如果找不到,输出"NO".
否则输出一行"YES".
然后紧接着3行,每一行两个整数表示xi,yi.
如果有多个答案,可以任意输出其中一组

思路

假设一个点是原点的话,三个点的坐标就是 (0,0)、(x,0)、(0,y)
又xy/2=nm/k
令x=n/k/gcd(2m,k),y=2m/gcd(2m,k)构造即可

代码

#include<cstdio>
#include<iostream> 
#include<cstring>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
int main(){
    ll n,m,k;
    cin>>n>>m>>k;
    if((2*n*m%k)!=0){
    	cout<<"NO";
        return 0;
	}
    int ke=1;
    if((k&1)==0)
        k/=2;
    else ke=0;
    ll ggg=gcd(n,k);
    ll x=n/ggg;k/=ggg;
    ll y=m/k;
    if(ke==0){
        if(ggg>=2)
			x*=2;
        else
			y*=2;
    }
    cout<<"YES"<<endl;
    cout<<0<<' '<<0<<endl;cout<<x<<' '<<0<<endl;cout<<0<<' '<<y<<endl;
    return 0;
}

D.采蘑菇

描述

赵老师降落在火星上的一片森林中,他想要带一些资源回地球进行研究
森林由两行组成,每行可分为n个连续空格。对于每个空格,赵老师都知道资源在这个空格中的生长速度(即每分钟在这个空格中生长多少克资源)。
赵老师可以用1分钟从一个格子移动到另一个相邻的格子中。赵老师不能离开这片森林。
如果两个单元格共用一个边,则认为它们是相邻的。
当赵老师进入某个格子中时,他将立即收集完所有生长在那里的资源。
赵老师从左上角的格子开始他的旅程。赵老师每分钟都必须移动到相邻的格子中,他不能等待资源生长。
他只想进入每个格子一次,并最大化收集到的资源的总重量。
最初,所有格子中的资源都是0。注意,赵老师不需要返回起始格子,且必须经过每一个格子一次。
求出最大总重量

输入

第一行包含一个正整数n (1 ≤ n ≤ 3·105) — 格子的长度.
第二行有 n个正整数 a1, a2, ..., an (1 ≤ ai ≤ 106) — 第一行中每个格子的生长速率.
第三行包含 n 个正整数 b1, b2, ..., bn (1 ≤ bi ≤ 106) — 第二行中每个格子的生长速率.

输出

输出一个整数 — 赵老师能采集到的最大资源量. 注意,每个格子赵老师最多且必须经过一次!

思路

因为格子是两行的,所以可以发现对于从某个节点停止的情况,路线有很大的规律,维护前缀和处理即可

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=300000+5;
ll a[2][maxn],pre[2][maxn];
ll suf[2][maxn],ipre[2][maxn],isuf[2][maxn];
int n;
int main(){
    scanf("%d",&n);
    ll sum1=0;
    for(int i=0;i<2;i++){
        for(int j=1;j<=n;j++){
            scanf("%lld", &a[i][j]);
			sum1+=a[i][j];
        }
    }
    for(int i=0;i<2;i++){
        for(int j=1;j<=n;j++) {
            pre[i][j]=pre[i][j-1]+a[i][j];
            ipre[i][j]=ipre[i][j-1]+pre[i][j];
        }
    }
    for(int i=0;i<2;i++){
        for(int j=n;j>=1;j--){
            suf[i][j]=suf[i][j+1]+a[i][j];
            isuf[i][j]=isuf[i][j+1]+suf[i][j];
        }
    }
    bool isup=0;
    ll sum=0,mx=-1e15;
    for(int i=1;i<=n;i++){
        if(isup==0){
            sum+=1ll *(2*i-1)*a[0][i];
            sum+=1ll *(2*i)*a[1][i];
            mx=max(mx,sum+isuf[1][i+1]+2ll *i*suf[1][i+1]+(ipre[0][n]-ipre[0][i])-pre[0][i]* 1ll *(n-i)+(n+i)*suf[0][i+1]);
            isup=1; 
        }
        else if(isup==1){
            sum+= 1ll *(2*i)*a[0][i];
            sum+= 1ll *(2*i-1)*a[1][i];
            mx=max(mx,sum+isuf[0][i+1]+ 2ll *i*suf[0][i+1]+(ipre[1][n]-ipre[1][i])-pre[1][i]*1ll*(n-i)+(n+i)*suf[1][i+1]);
            isup=0;
        }
    }
    sum=0;
    for(int i=1;i<=n;i++) 
		sum+=i*a[0][i];
    for(int i=n;i>=1;i--) 
		sum+=(n+(n-i+1))*a[1][i];
    mx=max(mx,sum);
    printf("%lld",mx-sum1);
    return 0;
}

E.矩阵

描述

现在小A正在参加一场数学考试。 为了赢得高分,小A想要猜出卷子里的矩阵!
小A只知道这个矩阵有n 行和m 列.。每一行, 他知道所有元素的异或之值。序列a1, a2, ..., an 分别代表第 1, 2, ..., n行所有元素的异或值. 同样的, 对每一列, 他也知道每一列所有元素的异或值。 序列b1, b2, ..., bm 分别代表第1, 2, ..., m列所有元素的异或值。
帮助小A找到一个符合条件的矩阵,或告诉他不存在这样的矩阵。

输入

第一行为两个整数n 和 m (2 ≤ n, m ≤ 100) — 矩阵的行数和列数。
第二行为n 个整数 a1, a2, ..., an (0 ≤ ai ≤ 109), 其中 ai 是每一行所有元素的异或值 i。
第三行有m 个整数 b1, b2, ..., bm (0 ≤ bi ≤ 109), 其中 bi是每一列所有元素的异或值 i。

输出

如果没有符合条件的矩阵,输出 "NO"(不加引号)。
否则, 第一行输出 "YES", 然后接下来的n行m 列输出ci1, ci2, ... , cim (0 ≤ cij ≤ 2·109) — 即符合条件的矩阵。
如果有多个矩阵符合条件,输出任意一个。

思路

我们知道
行异或和:suma = a[1]^a[2]^a[3]...^a[n].
列异或和:sumb = b[1]^b[2]^b[3]...^b[m].
这道题的判断为YES的充要条件就是:suma = sumb
由异或和的算法:x^a = b <==> x^b = a 知:
a[1]^a[2]^a[3] ... ^a[n-1] = a[n]^suma
b[1]^b[2]^b[3] ... ^b[m-1] = b[m]^sumb
------------------------| .
------------------------| .
------------------------O ---x

比如这两条线的交点为x 则要找到一个x(a[n]=b[m])使他满足:
x^a[1]^a[2]^a[3]^ ... ^a[n-1] = b[m]且
x^b[1]^b[2]^b[3]^ ... ^ b[m-1] = a[n]
也就是 x^suma^a[n] = b[m], 故 x = suma^a[n]^b[m].

代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=100+10;
int a[maxn],b[maxn];
int n,m,ke1,ke2;
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
		ke1 ^=a[i];
    }
    for(int i=1;i<=m;i++){
        scanf("%d",&b[i]);
        ke2 ^=b[i];
    }
    if(ke1 ^ ke2){
        printf("NO");
        return 0;
    }
	else
		printf("YES\n");
    for(int i=1;i<=n-1;i++){
        for(int j=1;j<=m-1;j++){
			printf("0 ");
		}
        printf("%d\n",a[i]);
    }
    for(int i=1;i<=m-1;i++){
        printf("%d ",b[i]);
    }
    int ans=b[m];
    for(int i=1;i<n;i++){
		ans^=a[i];
	}
    printf("%d",ans);
    return 0;
}

F.宇航员

描述

蕾蕾书记正计划带他的team——n个人去火星探险。其中最重要的任务是为队员提供食物。 仓库每天有m袋食物。每袋中都有一些ai类型的食物。 每位队员每天只吃一袋食物。由于极端的负荷,每个队员必须在整个探险过程中吃相同类型的食物。不同的队员可能会吃不同(或相同)类型的食物。 也就是说,对于每个队员j,蕾蕾书记应该选择他的食物类型bj,队员j每天会吃一个bj类型的食物包。不同队员的bj值可能不同。 按照上述要求,考察的最长可能天数是多少天?

输入

第一行包含两个整数n和m (1≤n,m≤100)队员的数量,可用的食品包的数量。 第二行包含整数序列a1,a2,…,am(1≤ai≤100), ai是i食品包的类型。

输出

输出一个整数——考察可以持续的天数。如果连一天都坚持不下去,输出0。

思路

枚举最长天数即可

代码

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=100+10;
int a[maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x;
		cin>>x;
		a[x]++;
	}
	for(int i=1;i<=m+1;i++){
		int cnt=0;
		for(int j=0;j<=100;j++){
			cnt+=a[j]/i;	
		}
		if(cnt<n){
			cout<<i-1;
			break;	
		}
	}
	return 0;
}

H.最大匹配

描述

给n个block,每个block表示为 [color1 | value | color2] 需要找出一个值最大的block序列,block序列的值定义为内部所有block的value和。一个block序列里,需要满足每个block的左颜色与左边元素的右颜色相同,每个block的左颜色和右颜色可以互换

输入

第一行输入n(1<=n<=100)
接下来n行 每行包含一个block的信息 color1, value, color2 (1<=color1,color2 <= 4, 1<=value <= 100000)

输出

输出最大的序列值

思路

这题正解应该是跑个欧拉图什么的,但是数据范围比较小,用暴力dp也可以,至于怎么dp由于过于暴力详见代码233333

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100+10;
int f[maxn][maxn][5][5];
int main(){
	int n,ans=0;
    cin>>n;
    memset(f,-0x3f,sizeof(f));
    for(int i=1;i<=n;i++){
        int a,b,c;
        cin>>a>>c>>b; 
        f[i][i][a][b]=f[i][i][b][a]=c;
    }
    for(int i=n;i;i--){
        for(int j=i;j<=n;j++){
            for(int x=1;x<=4;x++){
                for(int y=1;y<=4;y++){
                    for(int k=i;k<=j+1;k++){
                        f[i][j][x][y]=max(f[i][j][x][y],max(f[i][k][x][y],f[k+1][j][x][y]));
                        for(int p=1;p<=4;++p){
                            f[i][j][x][y]=max(f[i][j][x][y],max(f[i][k][p][y]+f[k+1][j][x][p],f[i][k][x][p]+f[k+1][j][p][y]));
                        }
                    }
                    ans=max(ans,f[i][j][x][y]);
                }
            }
        }
    }
    printf("%d",ans);
    return 0;
}

最后膜拜AK大佬dyp Orzzzzz