传送门:CF1120
一道贪心坑了好多人啊(包括我)。
首先存在以下情况是无解的:
不考虑 0 − 1 , 9 + 1 0-1,9+1 0−1,9+1的合法性,贪心依次让 1 − ( n − 1 ) 1-(n-1) 1−(n−1),即 ( 1 , b 1 − a 1 ) , ( 2 , b 2 − ( a 2 + ( b 1 − a 1 ) ) ) (1,b_1-a_1),(2,b_2-(a_2+(b_1-a_1))) (1,b1−a1),(2,b2−(a2+(b1−a1)))( ( i , j ) (i,j) (i,j)表示将 a i , a i + 1 a_i,a_{i+1} ai,ai+1同时 + j +j +j)。
a n a_n an是不能单独操作的,若 a n ≠ b n a_n\neq b_n an̸=bn,则必然无解。
可以证明除了上面的情况以外一定有解(证明略):
每次贪心操作最前可以操作的位置,步数一定是最少的。
SAM DP裸题,不解释。
转化一下题目的要求(设叶子结点个数为 k k k):,选择 k k k个结点,使得每个结点子树内不经过其它选择的结点所能到达的叶结点都是唯一且各不相同的,要求最小化 ∑ C i \sum C_i ∑Ci
考虑 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示以 i i i为根的子树还剩 0 / 1 0/1 0/1个叶子结点没被覆盖的最优答案。
答案即 f [ 1 ] [ 0 ] f[1][0] f[1][0]
如何寻找哪些点可能出现在最优点内呢?似乎可以再DP一遍。
考虑从状态 g [ 1 ] [ 0 ] g[1][0] g[1][0]出发进行 b f s bfs bfs(设 s u m u = ∑ v ∈ s o n u f [ v ] [ 0 ] sum_u=\sum \limits_{v\in son_u}f[v][0] sumu=v∈sonu∑f[v][0]):
故可以在线性时间内求出可能出现在在最优集合中的点。
这题的套路十分值得推广。
#include
#define rep(i,j,k) for(i=j;i<=k;++i)
#define pb push_back
#define mkp make_pair
#define fi first
#define sc second
#define pii pair
using namespace std;
const int N=2e5+10;
typedef long long ll;
int n,c[N],ans[N],f[N],num;
ll dp[N][2],sum[N];
bool vs[N][2],cn[N];
vector<int>g[N];
void dfs(int x)
{
if(f[x] && g[x].size()==1){
dp[x][0]=c[x];dp[x][1]=0;
return;
}
for(int y:g[x]) if(y^f[x]){f[y]=x;dfs(y);sum[x]+=dp[y][0];}
for(int y:g[x]) if(y^f[x]){
dp[x][0]=min(dp[x][0],sum[x]-dp[y][0]+dp[y][1]+c[x]);
dp[x][1]=min(dp[x][1],sum[x]-dp[y][0]+dp[y][1]);
}
dp[x][0]=min(dp[x][0],sum[x]);
}
queue<pii>que;
pii tp;
int main(){
memset(dp,0x7f,sizeof(dp));
int i,j,x,y;
scanf("%d",&n);
rep(i,1,n) scanf("%d",&c[i]);
rep(i,2,n){
scanf("%d%d",&x,&y);
g[x].pb(y);g[y].pb(x);
}
dfs(1);que.push(mkp(1,0));vs[1][0]=true;
for(;!que.empty();){
tp=que.front();que.pop();x=tp.fi;j=tp.sc;i=-1;
if(f[x] && g[x].size()==1){
if(!j) cn[x]=true;continue;
}
if((!j)&& dp[x][0]==sum[x]){
for(int y:g[x]) if((y^f[x])&&(!vs[y][0])){
vs[y][0]=true,que.push(mkp(y,0));
}
}
for(int y:g[x]) if(y^f[x])
if(((!j) && dp[x][0]==sum[x]-dp[y][0]+dp[y][1]+c[x])
||(j && dp[x][1]==sum[x]-dp[y][0]+dp[y][1])){
if(!j) cn[x]=true;
if(i==-1){
i=y;
for(int v:g[x]) if((v^f[x])&&(v^y)&&(!vs[v][0])){
vs[v][0]=true;que.push(mkp(v,0));
}
}else if(i<N){
if(!vs[i][0]) vs[i][0]=true,que.push(mkp(i,0));i=N+1;
}
if(!vs[y][1]) vs[y][1]=true,que.push(mkp(y,1));
}
}
rep(i,1,n) if(cn[i]) ans[++num]=i;
printf("%I64d %d\n",dp[1][0],num);
rep(i,1,num) printf("%d ",ans[i]);
return 0;
}
膜的这份代码
考虑是否能拆位处理:
由低位到高位枚举,设 f [ x ] [ i ] [ j ] [ 0 / 1 ] f[x][i][j][0/1] f[x][i][j][0/1]表示枚举到第 x x x位,前 x − 1 x-1 x−1位构成的数 × a \times a ×a后累积到当前位的需要加上的值为 i i i,前 x − 1 x-1 x−1位构成的数 × a \times a ×a的数位之和 × a \times a ×a与前 x − 1 x-1 x−1个数位之和的差为 j j j,是否所有数位均为0时的状态是否合法。
f [ x ] [ i ] [ j ] [ 0 / 1 ] = 1 f[x][i][j][0/1]=1 f[x][i][j][0/1]=1表示当前状态合法, f [ x ] [ i ] [ j ] [ 0 / 1 ] = 0 f[x][i][j][0/1]=0 f[x][i][j][0/1]=0表示不合法。
观察转移可以发现:
Orz
#include
using namespace std;
typedef long long ll;
const int N = 4008;
struct triple
{
int first, second, third;
};
bool vis[1000][N][2];
triple par[1000][N][2];
int dg[1000][N][2];
string solve(int a)
{
int kek = 2002;
queue <triple> q;
for (int dig = 0; dig < 10; dig++)
{
int ndig = (dig * a) % 10;
int nj = (dig * a) / 10;
bool flag = (dig != 0);
vis[nj][kek + ndig * a - dig][flag] = true;
par[nj][kek + ndig * a - dig][flag] = {-1, -1};
dg[nj][kek + ndig * a - dig][flag] = dig;
q.push({nj, kek + ndig * a - dig, flag});
}
while (!q.empty())
{
int i = q.front().first, j = q.front().second, flag = q.front().third;
if (i == 0 && j == kek && flag)
{
int x = i, y = j, z = flag;
string str = "";
while (x != -1)
{
if (!str.empty() || dg[x][y][z] != 0)
str += (dg[x][y][z] + '0');
auto go = par[x][y][z];
x = go.first, y = go.second,z = go.third;
}
return str;
//break;
}
q.pop();
for (int dig = 0; dig < 10; dig++)
{
int ndig = (i + a * dig) % 10;
int ni = (i + a * dig) / 10;
int nval = j + ndig * a - dig;
int nflag = (flag || (dig != 0));
if (nval >= 0 && nval < N && !vis[ni][nval][nflag])
{
vis[ni][nval][nflag] = true;
par[ni][nval][nflag] = {i, j, flag};
dg[ni][nval][nflag] = dig;
q.push({ni, nval, nflag});
}
}
}
return "-1";
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("a.in", "r", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int a;
cin >> a;
cout << solve(a) << endl;
}
很好的思维题,大力分析一波:
一旦 i i i时刻小W/小P把信放在了R那里,另一方取走信同时再放一封,这一方由取走信再放一封…也就是说到了 R R R之后,每轮 W , P W,P W,P交替时都需要去 R R R处。
(因为一封信在R处放到结束的花费是 C ( t n + 1 − t i ) C(t_{n+1}-t_i) C(tn+1−ti),如果每次交替放,总花费也是 C ( t n + 1 − t i ) C(t_{n+1}-t_i) C(tn+1−ti),R处更优,所以每轮都会去取,而且安排第一个去取最优)
考虑枚举 i i i表示从 i i i开始有信放在R那里,所有情况取 min \min min即可。
STO V–o_o–V
#include
using namespace std;
using ull = uint64_t;
using ll = int64_t;
using ld = long double;
const int N = 100228;
char tp[N];
ll a[N];
ll f[N];
const ll INF = (1ll << 60);
int main() {
#ifdef BZ
freopen("input.txt", "r", stdin);
#endif
ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cout.setf(ios::fixed); cout.precision(20);
int n;
ll c, d;
cin >> n >> c >> d;
for (int i = 0; i < n; ++i) {
cin >> a[i] >> tp[i];
}
cin >> a[n];
tp[n] = 'X';
ll ans = d * n;
ll s = 0;
ll last = a[n];
for (int i = n - 1; i >= 0; --i) {
if (tp[i] == tp[i + 1]) {
s += min(d, (last - a[i + 1]) * c);
} else {
last = a[i + 1];
}
ans = min(ans, c * (a[n] - a[i]) + s + d * i);
}
cout << ans << "\n";
}