Codeforces Global Round 10題解(A-D)
A. Omkar and Password
題目:http://codeforces.com/contest/1392/problem/A
題解:看似覺得有些難手,但是仔細思考下就會發現,只要整個陣列中有1個與其它不一樣,那麼最終都會合成為1個數字,只有全部一樣的數字才不能合成。
程式碼:
#include<bits/stdc++.h> //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 200010;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair <int, int> pii;
int main()
{
IOS;
ll t,n;
cin>>t;
while(t--)
{
cin>>n;
ll i,x;
bool flag=false;
ll sum=0;
for(i=1;i<=n;i++)
{
cin>>x;
if(i==1)
sum=x;
if(i!=1 &&x!=sum)
flag=true;
}
if(flag==true)
cout<<"1"<<endl;
else
cout<<n<<endl;
}
return 0;
}
B. Omkar and Infinity Clock
題目:http://codeforces.com/contest/1392/problem/B
題解:這是一道很簡單的陣列的值變化問題,最終只會有兩組陣列出現,只要根據K的奇偶性,來判斷應該輸出哪組陣列即可
程式碼:
#include<bits/stdc++.h> //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 200010;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
ll a[maxn],b[maxn];
int main()
{
ll t,n,k;
ll i,j;
cin>>t;
while(t--)
{
cin>>n>>k;
ll m=-1000000000;
for(i=1;i<=n;i++)
{
cin>>a[i];
m=max(m,a[i]);
}
ll f=-1000000000;
for(i=1;i<=n;i++)
{
a[i]=m-a[i];
f=max(f,a[i]);
}
for(i=1;i<=n;i++)
{
b[i]=f-a[i];
}
if(k%2==1)
{
for(i=1;i<=n;i++)
{
if(i!=1)
cout<<" "<<a[i];
else
cout<<a[i];
}
}
else
{
for(i=1;i<=n;i++)
{
if(i!=1)
cout<<" "<<b[i];
else
cout<<b[i];
}
}
cout<<endl;
}
return 0;
}
C. Omkar and Waterslide
題目:http://codeforces.com/contest/1392/problem/C
題解:一開始設想的是從前往後找,發現要考慮的東西太多了,之後看了看別人的程式碼後,發現從後往前找就非常的簡單了,只要當前的數比前一個小,就一直加到前一個數一樣大;如果比他小,就繼續比較那個數與他前面的數進行比較。最終得到的一定是一個單調不遞減陣列,並且操作次數也是最少的
程式碼:
#include<bits/stdc++.h> //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 200010;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
ll a[maxn];
int main()
{
IOS;
ll i,j,t,n;
cin>>t;
while(t--)
{
cin>>n;
ll ans=0;
for(i=1;i<=n;i++)
cin>>a[i];
for(i=n;i>=2;i--)
{
if(a[i]<a[i-1])
ans+=a[i-1]-a[i];
}
cout<<ans<<endl;
}
return 0;
}
D. Omkar and Bed Wars
題目:http://codeforces.com/contest/1392/problem/D
題解:
一位博主採用的貪心的思路:
對於一條單鏈LLL...L
或者RRR..RR
,顯然我們每3個翻轉一下即可,最小運算元ceil(n/3)ceil(n/3)ceil(n/3).
否則就會有形如RRRLLRRRLLL
的情況,我們把每個RRRLLL
這種形態稱為V
字.
顯然一個V
字谷底能消化掉4個元素RRLL
,然後我們剩下的兩邊的元素各進行一遍單鏈上的操作即可.
問題在於有沒有可能V字中R
鏈左端翻轉去和右邊的V字合併呢,答案是否定的.
因為如何翻轉的話就一定要消耗1次,而在原鏈上是期望用1/3
次而已(總之一定<1<1<1).
也就是說明這樣的牆頭草 棄暗投明 是不優的.
複雜度為O(n)O(n)O(n).
程式碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+10;
ll n,a[maxn],sta[maxn],top;
char s[maxn];
ll m;
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n;
scanf("%s",s+1);
int ans=0,sum=0;
for(int i=1;i<=n;i++)
{
s[i]=(s[i]=='R');
sum+=s[i];
s[i+n]=s[i];
}
if(!sum||sum==n)
{
cout<<(n+2)/3<<endl;
}
else
{
int i=1;
for(i=1;i<=n;i++)
{
if(s[i+1]&&!s[i])
{
i++;
break;
}
}
for(int j=i;j<=i+n-1;j++)
{
s[j-i+1]=s[j];
}
sta[top=1]=1;
for(i=2;i<=n;i++)
{
if(s[i]==s[i-1])
sta[top]++;
else
sta[++top]=1;
}
ans=0;
for(i=1;i<top;i+=2)
{
ans+=sta[i]/3+sta[i+1]/3;
}
if(i==top)
ans+=((sta[top]+2)/3);
cout<<ans<<endl;
}
}
return 0;
}
還有位博主的思是DP:
決策之間相互影響,dp比較好解決這類問題
但是1和n是特殊的元素,1和n相鄰,考慮起來很麻煩但是1和n是特殊的元素,考慮起來很麻煩
所以直接把1和n定義到狀態裡去所以直接把1和n定義到狀態裡去所以直接把1和n定義到狀態裡去
定義dp[i][j][q][k]為前i−1個人符合條件
且1號往j方向打人,n號往q方向打人,當前位置往k方向打人且1號往j方向打人,n號往q方向打人,當前位置往k方向打人且1號往j方向打人,n號往q方向打人,當前位置往k方向打人
但這麼設計狀態是有問題的,有後效性
我們知道一個位置不合法當且僅當前面,自己,後面打人的方向一致
也就是自己只被一個人打,而且自己沒有打他,這是不合法的
而對於當前狀態,根本沒記錄被幾個人打
後一個人轉移的時候無法判斷前一個位置是否合法
正確的狀態還要加一維自己被打的資訊
dp[i][j][q][k][w]定義前i−1個位置合法
1號往j打,n號往q打,自己往k打,w表示是否被上一個人打
這樣轉移就很方便
舉個例子,dp[3][0][1][0][0]的含義是(0表示左,1表示右)
前2個位置合法,1號往左打,n號往右打,自己往左打,且自己沒有被左邊打
那麼此時轉移是那麼此時轉移是那麼此時轉移是
dp[3][0][1][0][0]=dp[2][0][1][0][1]
由於3號沒被2號打,那麼2號一定是往左打
且二號一定被左邊的人打
如果不被左邊的人打,就出現矛盾.因為此時2號只被3號打卻沒有打3號
程式碼:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n,t,dp[maxn][2][2][2][2];
char a[maxn],b[maxn];
int main()
{
cin >> t;
while( t-- )
{
cin >> n;
cin >> (a+1);
//dp[i][j][q][k][s] 前i滿足且1,n分別往j,q方向打人,s表示是否被左邊打
for(int i=1;i<=n;i++)
for(int j=0;j<=1;j++)
for(int q=0;q<=1;q++)
for(int k=0;k<=1;k++)
for(int w=0;w<=1;w++)
dp[i][j][q][k][w]=1e9;
//下面是初始化
dp[1][0][0][0][0]=( a[1]!='L' )+( a[n]!='L');
dp[1][0][1][0][1]=( a[1]!='L' )+( a[n]!='R');
dp[1][1][0][1][0]=( a[1]!='R' )+( a[n]!='L');
dp[1][1][1][1][1]=( a[1]!='R' )+( a[n]!='R');
for(int i=2;i<n;i++)
for(int j=0;j<=1;j++)
for(int q=0;q<=1;q++)
{
int s=1;
if( a[i]=='L' )//往左邊打
{
dp[i][j][q][0][0]=dp[i-1][j][q][0][1];//一定被左邊打
dp[i][j][q][0][1]=min(dp[i-1][j][q][1][0],dp[i-1][j][q][1][1] );
dp[i][j][q][1][0]=min(dp[i-1][j][q][0][0],dp[i-1][j][q][0][1])+s;
dp[i][j][q][1][1]=dp[i-1][j][q][1][0]+s;
}
else
{
dp[i][j][q][0][0]=dp[i-1][j][q][0][1]+s;
dp[i][j][q][0][1]=min(dp[i-1][j][q][1][0],dp[i-1][j][q][1][1] )+s;
dp[i][j][q][1][0]=min(dp[i-1][j][q][0][0],dp[i-1][j][q][0][1]);
dp[i][j][q][1][1]=dp[i-1][j][q][1][0];
}
}
int ans=1e9;
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
{
if( i==0&&j==0&&k==0)//n只被1個人打卻沒有還手,不合法
continue;
if( i==1&&j==1&&k==1 )//n只被1個人打卻沒有還手,不合法
continue;
if( !(j==0&&k==0) )//除去n-1位置不合法的情況
ans=min(ans,dp[n-1][i][j][k][0]);//n-1不被左邊打
if( !(j==1&&k==1) )//除去n-1位置不合法的情況
ans=min(ans,dp[n-1][i][j][k][1]);//n-1被左邊打
}
cout << ans << endl;
}
}
總的來說DP還是稍微能夠理解,貪心的思路真的是望塵莫及額