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個點,是一個手的形狀,問是左手還是右手。
解法
這裡放的是訓練時的比較“野”的一種做法:
題圖如上,因為左手和右手是鏡面對稱的,所以不同點是很容易找到的;另外,給定的點是順時針或者逆時針給出的,所以要是想根據點的輸入判斷左手還是右手是不可能的。可以選擇凸包什麼的,但是當時看到題面裡面提到,手的形狀是不會放大或者縮小的,但是可以旋轉和平移,平移的話,所有的點加或者是減都是一樣的形狀。
所以選定了(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
題意
解法
程式碼
//將內容替換成程式碼
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;
}
}
}