演算法訓練(二)
1.zoj-4026
首先我們需要推倒一下,每次都是從A開始拿,可以列舉幾種情況,會發現最後結束一定是在第四次抽到A的時候結束的,因為假設在第二堆結束,你在抽完4次2後第二堆才為空,你需要第五次抽到2才會回到第二堆,這時候才能結束,不符合題意,因此只有第一次就被抽的A能結束遊戲,因此結束的時候一定為A,因此A被抽完的可能性為ans【1】=1;
假設每種牌剩餘數量分別為n1,...,n12n1,...,n12,對於2≤i≤122≤i≤12,如果ni=0ni=0說明四張ii都被拿走了,即ans[i]=1ans[i]=1,否則說明還有ii沒有被拿走,此時若n1=0n1=0說明遊戲已經結束,那麼沒有機會再拿走剩下的ii了,即ans[i]=0ans[i]=0,否則說明還有機會拿走全部的ii,此時問題轉化為,m=48−nm=48−n張牌隨意排列,要求在拿走第n1張AA之前拿走ni張ii,列舉最後一張A的位置j,用組合公式算出總方案數和滿足條件的方案數,程式碼如下:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int, int>P;
const int INF = 0x3f3f3f3f, maxn = 15;
int T, n, num[maxn];
ll C(int n, int m)
{
if (m<0 || m>n)return 0;
ll ans = 1;
for (int i = 1; i <= m; i++)ans = ans * (n - m + i) / i;
return ans;
}
int V(char *s)
{
if (s[1])return 10;
if (s[0] == 'A')return 1;
if (s[0] == 'J')return 11;
if (s[0] == 'Q')return 12;
return s[0] - '0';
}
ll gcd(ll a, ll b)
{
return b ? gcd(b, a%b) : a;
}
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 1; i <= 12; i++)num[i] = 4; /*每張牌有4張*/
for (int i = 1; i <= n; i++)
{
char s[3];
scanf("%s", s);
num[V(s)]--;/*每次被抽一次就減少一張*/
}
n = 48 - n;/*剩下多少張牌沒有被抽*/
printf("1");/*A被抽完的可能性為1*/
for (int i = 2; i <= 12; i++)
if (!num[i])printf(" 1");/*若已被抽完則為1*/
else if (!num[1])printf(" 0");/*若A已被抽完且第i張牌沒有被抽完就已沒機會被抽完*/
else
{
ll q = C(n, num[1])*C(n - num[1], num[i]);/*總的方案數*/
ll p = 0;
for (int j = num[1] + num[i]; j <= n; j++)p += C(j - 1, num[1] - 1)*C(j - num[1], num[i]);/*滿足條件的方案數*/
ll g = gcd(p, q);
p /= g, q /= g;
if (p == 0)printf(" 0");
else if (p == q)printf(" 1");
printf(" %lld/%lld", p, q);
}
printf("\n");
}
return 0;
}
2.zoj-4027
假設現有n個括號,其中有兩個左括號A和B,則最後移動完後A一定在B的左邊,這是很關鍵的一點;
可知左括號與n個右括號交換,則得到的值是此左括號對應的值與被交換的右括號的值的總和;
dp陣列的i行表示的是第i個左括號,因此這一行用來維護此左括號能得到的最大值,再與dp[i+1][j+1]相加,即可得到這兩個左括號能得到的最大值的總和,建議畫出dp圖理解,程式碼如下:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
const int maxn = 1005;
int T, n, nl, nr, val[maxn], num[maxn], sum[maxn], pos[maxn];
long long dp[maxn][maxn];
char s[maxn];
int main()
{
scanf("%d", &T);
while (T--)
{
memset(s, '\0', sizeof(val));
memset(val, 0, sizeof(val));
memset(pos, 0, sizeof(pos));
memset(num, 0, sizeof(num));
memset(sum, 0, sizeof(sum));
memset(dp, 0, sizeof(dp));
scanf("%d", &n);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++)
scanf("%d", &val[i]);
nl = nr = 0;/*nl用來記錄左括號的總數,nr用來記錄右括號的總數*/
for (int i = 1; i <= n; i++)
if (s[i] == '(')
{
pos[++nl] = i;/*當出現左括號的時候,用pos陣列來記錄此括號的位置*/
num[nl] = nr;/*用num陣列來記錄此括號的左邊的右括號的總數*/
}
else
{
nr++;
sum[nr] = sum[nr - 1] + val[i];/*用sum陣列記錄右括號的疊加和*/
}
for (int i = nl; i >= 1; i--)
{
for (int j = n - (nl - i); j >= pos[i]; j--)
{
long long temp = (long long)val[pos[i]] * (sum[num[i] + j - pos[i]] - sum[num[i]]);
dp[i][j] = dp[i + 1][j + 1] + temp;
if (j<n - (nl - i))dp[i][j] = max(dp[i][j], dp[i][j + 1]);
}
for (int j = pos[i] - 1; j >= pos[i - 1]; j--)
dp[i][j] = dp[i][j + 1];
}
long long ans = 0;
for (int j = 1; j <= n; j++)
ans = max(ans, dp[1][j]);
printf("%lld\n", ans);
}
return 0;
}
3.zoj-4033
當 0<=i<n/2 時,將標記為0的男生分到3組,女生分到1組;
當 i >= n/2 時,將標記為1的男生分到3組,女生分到一組;
分別對1、3組和2、4組的權值求和,若不相等輸出-1,否則輸出位置。
#include <cstdio>
#include <algorithm>
#include <functional>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int maxn=1e6+10,inf=(1<<30),mod=1e9+7;
char que[maxn];
int place[maxn];
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
scanf("%s",que);
int sum1=0,sum2=0;
for (int i=0;i<n/2;i++){
if(i%2==0) place[i]=0,sum1+=i+1;
else place[i]=1,sum2+=i+1;
}
for (int i=n/2;i<n;i++){
if(i%2==0) place[i]=1,sum2+=i+1;
else place[i]=0,sum1+=i+1;
}
if(sum1!=sum2){
printf("-1\n");
continue;
}
for (int i=0;i<n;i++){
if(que[i]=='1'){
if(place[i]) printf("4");
else printf("3");
}
else {
if(place[i]) printf("2");
else printf("1");
}
}
printf("\n");
}
return 0;
}