E.
题意:给定一个初始字符串,可以把它接龙下去,最后无法操作的人判负。若双方都采用最优策略,即先保证自己不输,再击败对方,输出博弈结果。 n<=2e5
。
Solution:
这是一道图论的博弈问题,考虑回溯 + 逆推。
首先这个平局很难判断,我们不妨先求出必胜或必败的状态,如果两者都不是,则说明是平局状态。首先初度为零是必败态,其次,如果后继有一个必败态,则它为必胜态;如果后继全为必胜态,则它为必败态。
这个环比较难搞,不妨看下图:
这是环上都有值的情况。有时候会形成一个封闭的环,双方都不愿意走出这个环,否则都是自己输,如下图:
那么此时环上的状态都是平局了。
最后考虑优化建边,不难想到将首和尾连边,这样出发点就是尾字符。
时间复杂度 O(n)
。
#include<bits/stdc++.h>
#define ins insert
#define mp make_pair
#define pii pair<int,int>
#define pb push_back
#define vec vector
using namespace std;
const int mx=2e5+5;
const int mxn=52*52*52;
int n,m,dp[mxn],deg[mxn];
string s;
vec<int> son[mx];
pii edge[mx];
queue<int> Q;
int char_to_int(char c) {
if('A'<=c&&c<='Z') return c-'A';
else return c-'a'+26;
}
int string_to_int(string s) {
for(auto &c:s) {
c=char_to_int(c);
}
return s[0]*52*52+s[1]*52+s[2];
}
//思路:回溯+逆推
int main() {
// freopen("data.in","r",stdin);
memset(dp,-1,sizeof(dp));
ios_base::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++) {
cin>>s;
int x=string_to_int(s.substr(0,3)),y=string_to_int(s.substr(s.size()-3,3));
// cout<<x<<" "<<y<<endl;
edge[i]=mp(x,y);
son[y].pb(x),deg[x]++;
// appear[id[i]]=1;
}
int Lose=0,Win=1;
for(int i=0;i<mxn;i++) {
if(!deg[i]) {
dp[i]=Lose;
Q.push(i);
}
}
while(Q.size()) {
int x=Q.front(); Q.pop();
if(dp[x]==Win) {
for(auto y:son[x]) {
if(~dp[y]) continue;
if(--deg[y]==0) {
dp[y]=Lose;
Q.push(y);
}
}
}
else {
for(auto y:son[x]) {
if(~dp[y]) continue;
dp[y]=Win;
Q.push(y);
}
}
}
for(int i=1;i<=n;i++) {
if(dp[edge[i].second]==Lose) cout<<"Takahashi"<<endl;
else if(dp[edge[i].second]==Win) cout<<"Aoki"<<endl;
else cout<<"Draw"<<endl;
}
}
F.
题意:给定 n
个数以及高度,砍掉一棵树的代价为 h[i-1]+h[i]+h[i+1]
,同时令 h[i]=0
。求一个砍树的排列,使得总花费最少。求排列的个数。
Solution:
考虑每一棵树 i
,如果它相邻的树比它先砍,则自己每次要增加代价 h[i]
。
这样,对于 1<=i<=n-1
,如果 h[i]<h[i+1]
,则 i+1
先砍最优;如果 h[i]>h[i+1]
,则 i
先砍最优。
那么令 dp[i][j]
前 i
棵树的所有排列数,使得树 i
是要砍掉的第 j
个元素 ,这里有隐含条件 j<=i
。不难写出转移式,通过前缀和优化,时间复杂度 O(n^2)
。
这种为每个转换插入元素的 DP 结构称为“插入 DP”。
#include<bits/stdc++.h>
#define ll long long
#define ins insert
#define mp make_pair
#define pii pair<int,int>
#define pb push_back
#define vec vector
using namespace std;
const int mx=4005;
const int mod=1e9+7;
ll dp[mx][mx],res;
int n,a[mx];
//dp[i][j] 前 i 棵树的所有排列数,使得树 i 是要砍掉的第 j 个元素
int main() {
// freopen("data.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
dp[1][1]=1;
for(int i=2;i<=n;i++) {
for(int j=1;j<=i;j++) {
if(a[i-1]<a[i]) {
dp[i][j]=(dp[i][j]+dp[i-1][i-1]-dp[i-1][j-1])%mod;
}
else if(a[i-1]>a[i]) {
dp[i][j]=(dp[i][j]+dp[i-1][j-1])%mod;
}
else {
dp[i][j]=(dp[i][j]+dp[i-1][i-1])%mod;
}
dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
// printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
}
}
res=dp[n][n];
if(res<0) res+=mod;
printf("%lld",res);
}