zoukankan      html  css  js  c++  java
  • [NOI2020] 制作菜品

    CIL.[NOI2020] 制作菜品

    本题有三个难点:留意到题面中的 \(n-2\leq m\);证明;想到 bitset 优化。

    首先,在很隐蔽的角落,有一句话 \(n-2\leq m\leq 5000\)。假如没看到这句话,就乖乖爆零罢。

    结论1. \(m\geq n-1\) 时一定有解。

    要证明这个结论,我们要分成三部分。

    结论1.1. \(m=n-1\) 时一定有解。

    不妨将 \(a\) 数组递增排序。则,\(a_1\) 必定 \(<k\),因为所有数的平均值为 \(\dfrac{mk}{n}=\dfrac{(n-1)k}{n}<k\),则必有至少一个数满足 \(<k\) 的条件。

    同时,我们也能发现,\(a_1+a_n>k\)。因为,若 \(a_1+a_n\leq k\),则 \(mk=\sum\limits_{i=1}^na_i\leq d_1+\sum\limits_{i=2}^na_n\leq a_1+(n-1)(k-a_1)=(n-1)k-(n-2)a_1=mk-(n-2)a_1\)。显然,当 \(n>2\) 时,因为 \(a_1>0\),所以 \((n-2)a_1>0\),故可以一次填完所有 \(a_1\),然后不足的部分就用 \(a_n\) 补,这样便同时少了一种原料和一道菜,转移到了 \(n-1\) 的情形;而当 \(n=2\) 时,因为只要做一道菜,所以直接把剩余的两种原料怼一块即可。

    结论1.2. \(m=n\) 时一定有解。

    不妨仍将 \(a\) 数组递增排序。则,\(a_n\) 必定 \(\geq k\),因为所有数的平均值为 \(k\),则必有至少一个数满足 \(\geq k\) 的条件。

    \(a_n=k\),则可以用 \(a_n\) 单独做一道菜,转移到 \(n,m\) 均减少 \(1\) 的情形,这样不断递归下去,直到 \(n,m\) 均为 \(0\)(此时已经构造出一组解)或是 \(a_n>k\)。当 \(a_n>k\) 时,仍可以用 \(a_n\) 单独做一道菜,转移到 \(m=n-1\) 的情形。

    结论1.3. \(m>n\) 时一定有解。

    递增排序后,\(a_n\) 必定 \(>k\),因为所有数的平均值 \(>k\),则必有至少一个数 \(>k\)。于是可以用 \(a_n\) 单独做一道菜,转移到 \(m\) 减一的情形。不断执行,直到到达 1.2 的场景。

    结论2. \(m=n-2\) 时,当且仅当其能被分成两个非空集合 \(\mathbb{U,V}\) 使得 \(\sum\limits_{u\in\mathbb U}a_u=\Big(|\mathbb U|-1\Big)k\) 时,有解。

    首先,其是充分的,因为对于 \(\mathbb{U,V}\) 我们可以分别应用 1.1 中的结论直接构造出一组解来。

    其次,其是必要的,因为 \(n-2\) 条边连不出一张连通图,必定可以将所有原料分作两个集合使得不存在任何同时包含来自两个集合的原料的菜。

    这样,我们就可以背包出一组解来了。具体而言,有经典套路是在上式中把 \(k\) 移到左侧,得到 \(\sum\limits_{u\in\mathbb U}(a_u-k)=-k\)。暴力背包复杂度是 \(O(n^3k)\) 的。但是,因为01背包的数组是 bool 数组,所以可以使用 bitset 优化至 \(\dfrac{n^3k}{\omega}\)

    (可能因为 bitset 开太大的缘故,本代码在 Windows 下会 RE,可以使用 CF 的 Custom Test 编译)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int T,n,m,p,a[510],ord[510];
    vector<vector<int> >v;
    bool cmp(int u,int v){return a[u]<a[v];}
    bitset<5000010>bs[502];
    const int half=2500002;
    bool sd[510];
    void SOLVE(int M,int N,int *dro){
    	while(M){
    		sort(dro+1,dro+N+1,cmp);
    		v.push_back({dro[1],a[dro[1]],dro[N],p-a[dro[1]]});
    		a[dro[N]]-=p-a[dro[1]];
    		dro[1]=dro[N--],M--;
    	}
    }
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		scanf("%d%d%d",&n,&m,&p),v.clear();
    		for(int i=1;i<=n;i++)scanf("%d",&a[i]),ord[i]=i;
    		while(m>n){
    			sort(ord+1,ord+n+1,cmp);
    			v.push_back({ord[n],p}),a[ord[n]]-=p;
    			m--;
    		}
    		while(n&&m==n){
    			sort(ord+1,ord+n+1,cmp);
    			v.push_back({ord[n],p}),a[ord[n]]-=p;
    			m--;
    			if(!a[ord[n]])n--;
    		}
    		if(m==n-1)SOLVE(m,n,ord);else if(n){
    			bs[0].reset(),bs[0].set(half);
    			for(int i=1;i<=n;i++){
    //				printf("%d\n",i);
    				if(a[i]==p)bs[i]=bs[i-1];
    				if(a[i]>p)bs[i]=bs[i-1]|(bs[i-1]<<(a[i]-p));
    				if(a[i]<p)bs[i]=bs[i-1]|(bs[i-1]>>(p-a[i]));
    			}
    			if(!bs[n].test(half-p)){puts("-1");continue;}
    			for(int i=n,now=half-p;i;i--){
    				if(bs[i-1].test(now-a[i]+p))sd[i]=true,now=now-a[i]+p;
    				else sd[i]=false;
    			}
    //			for(int i=1;i<=n;i++)printf("%d ",sd[i]);puts("");
    			sort(ord+1,ord+n+1,[](int x,int y){return sd[x]<sd[y];});
    			for(int i=1;i<n;i++)if(!sd[ord[i]]&&sd[ord[i+1]])SOLVE(i-1,i,ord),SOLVE(n-i-1,n-i,ord+i);
    		}
    		for(auto i:v){for(auto j:i)printf("%d ",j);puts("");}
    	}
    	return 0;
    } 
    

  • 相关阅读:
    【译】Using .NET for Apache Spark to Analyze Log Data
    边缘缓存模式(Cache-Aside Pattern)
    GUID做主键真的合适吗
    在Java大环境下.NET程序员如何夺得一线生机
    板子
    P1525 关押罪犯 (并查集 / 二分图)| 二分图伪码
    算法学习笔记:匈牙利算法
    POJ
    19级暑假第三场训练赛
    POJ 1011 Sticks​ (DFS + 剪枝)
  • 原文地址:https://www.cnblogs.com/Troverld/p/14601729.html
Copyright © 2011-2022 走看看