[NOI2020] 制作菜品
一、题目
二、解法
独立思考确实挺有趣的,虽然花了很久时间但还是挺开心的。
看到这题没什么思路,可以多看几遍题,发现关键条件 \(n-2\leq m\),还是不怎么会,直接开部分分。
\(n-1=m\) 怎么做啊?也就是原料数正好多一个,如果某个原料不足 \(k\),那么直接把他选完,再找到最大的原料选剩下的部分,这样可以一直递归下去,每次可以减少一个原材料,而且最后一定会剩下两个原材料和一次机会,这种情况一定有解,因为无论怎样最大的都可以让你凑足 \(k\)
\(n-1\leq m\) 都一样啊,略微改一下就行了,如果全部原料都大于等于 \(k\) 那么随便找一个减少 \(k\) 即可。
发现现在只剩下 \(n-2=m\) 了,但是不能直接用 \(n-1=m\) 的方法,这样会出问题:最后可能会剩三个原材料,而且每次不一定就能减少一个原材料(最大值不够顶)。
但是可以把它分成不相关的两个集合,每个集合用 \(n-1=m\) 的方法做即可,设划分的集合为 \(S\),那么有解当且仅当满足:\(\sum d_i=(|S|-1)\cdot k\),如果满足这个条件一定有解(充分性),现在考虑证明一下必要性:
仔细观察我们的基础问题 \(n-1=m\),可以看成 \(n-1\) 条边联通了 \(n\) 个点,因为构造的过程反应到图上就是给每个点找父亲,\(n-2=m\) 就意味着至少有两个连通块,那一定会有至少一个连通块满足点数等于边数减一,我们把剩下的点看成另一个连通块即可,这两个连通块是独立的,可以适用于"树"的必要条件。
现在的问题就是划分出一个满足 \(\sum d_i=(|S|-1)\cdot k\) 的集合了,用类似分数规划的技巧可以转化成 \(\sum d_i-k=-k\),那么设 \(dp[i][j]\) 表示考虑前 \(i\) 个原料 \(d_i-k\) 的总和为 \(j\) 是否合法,可以用 \(\tt bitset\) 优化这个 \(01\) 背包,时间复杂度 \(O(T\frac{n^2k}{w})\)
然后把 \(dp\) 路径还原一下就行了,所以除了一开始的结论剩下都是简单的技巧。
#include <cstdio>
#include <vector>
#include <bitset>
#include <set>
using namespace std;
const int M = 505;
const int N = 2500000;
#define fi first
#define se second
#define make make_pair
#define pii pair<int,int>
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,k,d[M],vis[M];vector<int> v;
bitset<10000*M> dp[M];
void solve()
{
set<pii> s;
for(int i=0;i<v.size();i++)
s.insert(make(d[v[i]],v[i]));
while(!s.empty())
{
pii t1=*s.begin(),t2=*s.rbegin();
s.erase(t1);
while(t1.fi>=k)
t1.fi-=k,printf("%d %d\n",t1.se,k);
if(!t1.fi) continue;
s.erase(t2);
int tmp=k-t1.fi;
printf("%d %d %d %d\n",t1.se,t1.fi,t2.se,tmp);
t2.fi-=tmp;
s.insert(t2);
}
v.clear();
}
void devide()
{
dp[0].reset();dp[0][N]=1;
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1];
if(d[i]>k)
dp[i]|=dp[i-1]<<(d[i]-k);
else
dp[i]|=dp[i-1]>>(k-d[i]);
}
if(!dp[n][N-k]) puts("-1");
else
{
int x=n,y=N-k;
while(x)
{
if(dp[x-1][y-(d[x]-k)])
{
y-=(d[x]-k);
vis[x]=1;
v.push_back(x);
}
x--;
}
solve();
for(int i=1;i<=n;i++)
if(!vis[i]) v.push_back(i);
solve();
}
}
signed main()
{
T=read();
while(T--)
{
n=read();m=read();k=read();
for(int i=1;i<=n;i++)
d[i]=read(),vis[i]=0;
if(m!=n-2)
{
for(int i=1;i<=n;i++)
v.push_back(i);
solve();
continue;
}
devide();
}
}