发布于 

Codeforeces Edu 37 D

Codeforeces Edu 37 D

|这题真的是一点思路也没有,好不容易看懂了题解写完了,结果发现理解出了偏差(;´д`)ゞ

题目来源: Codeforces

题解

思路

在计算怎么转移之前,我们需要先判断是否能够转移出水的体积为V的水桶。

那么,首先我们可以得出,对于$ sum = ^n_{i=1}{v[i]} \(,若\)sum<V$则不可能。

然后我们判断什么时候可以转移。我们可以发现当$ V%k == 0$时,我们永远可以得到一个水的体积为V的水桶。

而当V不是k的整数倍时,V\(V\%k\)这一部分肯定能够从其它桶凑出。只有此时能够得到一个体积为V的水桶。

然后我们便需要算出是否有几个水桶水量的和能够与V同余。这里我们需要用到简单的动态规划(但我还是把它忘了。。。)。使用dp[i][j]记录到第i个数是否能取到同余kj的值。

最后只要输出移动方法就行了。因为改题目非固定答案,所以我们可以"套模板",即使用麻烦但是能够达成效果的方法进行移动。

我们可以使用一个flag[i][j]来记录在到达位置i%k==j时是否取了i这个位置的数。

然后我们把所有取了的数移到一个位置x,没有取的移到一个位置y,再用yx中的水加至体积为V便可以了。 ### 结论

判断

  1. \(sum < V\)时: NO
  2. \(sum\%k==0\)时: YES
  3. $ S\(使得\)_{iS}{v[i]} V(k)$: YES
  4. Otherwise: NO

(\(S\)为取出的数的下标的集合)

第2中情况其实可以直接被第三种情况包括,只要初始从dp[0][0] = true开始即可。

DP

我们使用dp[i][j]来表示到第i个数时能否有取法S使得\(\sum_{i\in S}{v[i]} \equiv V(\mod k)\)。那么转移方程如下: \[ dp[i][j] = dp[i-1][j]\ || \ dp[i-1][Mod(j-v[i],k)]\]

其中或运算符左边的是不取当前数值的情况,右边是取当前数值的情况。

这里使用自己写的Mod()函数是为了防止CPP%算出负值。

f[i][Mod(j-v[i],k)]Trueflag[i][j]True

转移步骤

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <cstdio>
#include <cstring>

bool dp[5010][5010];
bool flag[5010][5010];
int v[5010];

int Mod(int a,int b)
{
truewhile (a<0)
truetruea += b;

truereturn a%b;
}

int main()
{
truememset(dp,false,sizeof(dp));
truememset(flag,false,sizeof(flag));

trueint n,k,V,sum = 0;
truescanf("%d%d%d",&n,&k,&V);

truefor (int i =1; i<=n; i++)
true{
truetruescanf("%d",&v[i]);
truetruesum += v[i];
true}

trueif (sum<V) //sum<V时输出NO
true{
truetrueprintf("NO\n");
truetruereturn 0;
true}

truedp[0][0] = true; //初始值
truefor (int i = 1; i<=n; i++)
truetruefor (int j = 0; j<k; j++)
truetrue{
truetruetrueif (dp[i-1][j]) //当前数值不取的情况
truetruetruetruedp[i][j] = true;
truetruetrueelse if (dp[i-1][Mod(j-v[i],k)]) //取当前数值的情况
truetruetrue{
truetruetruetruedp[i][j] = true;
truetruetruetrueflag[i][j] = true;
truetruetrue}
truetrue}
true/*
for (int i = 1; i<=n; i++)
{
for (int j = 0; j<k; j++)
printf("%d ",dp[i][j]);
printf("\n");
}
*/
trueif (dp[n][V%k])
true{
truetrueprintf("YES\n");

truetrueint t1=0,t2=0; //分别代表取了的数和没取的数转移到的位置

truetrueint j = V%k,i=n;
truetruewhile(i>0)
truetrue{
truetruetrueif (v[i]==0)
truetruetrue{
truetruetruetruei--;
truetruetruetruecontinue;
truetruetrue}
truetruetrueif (flag[i][j])
truetruetrue{
truetruetruetruej = Mod(j-v[i],k);
truetruetruetrueif (!t1)
truetruetruetruetruet1 = i;
truetruetruetrueelse{
truetruetruetruetrueprintf("%d %d %d\n",(v[i]+k-1)/k,i,t1);
truetruetruetruetruev[t1] += v[i];
truetruetruetruetruev[i] = 0;
truetruetruetrue}
truetruetrue}else{
truetruetruetrueif (!t2)
truetruetruetruetruet2 = i;
truetruetruetrueelse{
truetruetruetruetrueprintf("%d %d %d\n",(v[i]+k-1)/k,i,t2);
truetruetruetruetruev[t2] += v[i];
truetruetruetruetruev[i] = 0;
truetruetruetrue}
truetruetrue}
truetruetruei--;
truetrue}

truetrueif (V%k==0 && V!=0)
truetrue{
truetruetrueif (t2!=1) t1=1;
truetruetrueelse t1 = 2;
truetruetrueprintf("%d %d %d\n",V/k,t2,t1);
truetrue}
truetrueelse if (t1 && V!=v[t1])
truetrue{
truetruetrueif (!t2)
truetruetrue{
truetruetruetrueif (t1==1) t2=2;
truetruetruetrueelse t2 = 1;
truetruetrue}
truetruetrueif (v[t1]<V)
truetruetruetrueprintf("%d %d %d\n",(V-v[t1])/k,t2,t1);
truetruetrueelse printf("%d %d %d\n",(v[t1]-V)/k,t1,t2);
truetrue}

true}else printf("NO");
}

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 [@Songer](https://blog.songer.xyz/) 创建,使用 Stellar 作为主题。