题解 P6775 【[NOI2020]制作菜品】
随机化跑得飞快
总共300ms,还没开O2,全站第一,第二600+ms!
Case1:
必然有解,贪心拿最小的与最大的匹配即可。
由抽屉原理,最小的
Case2:
随便匹配,直至
Case3:
就是划分为两个集合,使
于是,伟大的随机化横空出世
但是直接随机会WA惨,或者被构造数据卡死。
令
得到一个集合的划分后,将两个集合的负数对调就可以得出一组解。
证明:
充分性:设划分出集合
所有
所以:
把两个集合
因为所有
必要性:设划分出集合
调换负数前:
调换负数后:
因为
证毕。
复杂度降至一亿,而且跑出一组解就会直接退出,常数也不大。
代码并不难写:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,k;
struct B{
int id,wei,val;
}a[500],b[500];
void f(int l,int r)
{
int mn=-1,mx=-1;
for(int i=l;i<=r;i++)
if(mn==-1||b[i].wei<b[mn].wei)
mn=i;
swap(b[l],b[mn]);
for(int i=l;i<=r;i++)
if(mx==-1||b[i].wei>b[mx].wei)
mx=i;
swap(b[r],b[mx]);
return;
}
void solve(int l,int r)
{
if(l==r) return;
if(l+1==r){
if(b[l].wei==0) printf("%d %d\n",b[r].id,k);
else if(b[r].wei==0) printf("%d %d\n",b[l].id,k);
else printf("%d %d %d %d\n",b[l].id,b[l].wei,b[r].id,b[r].wei);
}
else{
f(l,r);
printf("%d %d %d %d\n",b[l].id,b[l].wei,b[r].id,k-b[l].wei);
b[r].wei-=k-b[l].wei;
solve(l+1,r);
}
return;
}
#include<ctime>
#include<cstdlib>
int find(void)
{
int sum=0,pos=1;
for(int i=0;i<n;i++){
a[i].wei=b[i].wei-k; a[i].id=b[i].id;
a[i].val=max(a[i].wei,-a[i].wei);
sum+=a[i].val;
}
srand(time(NULL));
int tar=sum>>1,lim=4*n*k,Now=a[0].val;
while(lim--){
if(Now==tar) break;
if(Now>tar){
int t=rand()%pos;
Now-=a[t].val; swap(a[t],a[--pos]);
}
else{
int t=rand()%(n-pos)+pos;
Now+=a[t].val; swap(a[t],a[pos++]);
}
}
if(lim<0) return -1;
int l=0,r=n;
for(int i=0;i<pos;i++)
if(a[i].wei>=0) b[l++]=a[i];
else b[--r]=a[i];
for(int i=pos;i<n;i++)
if(a[i].wei>=0) b[--r]=a[i];
else b[l++]=a[i];
for(int i=0;i<n;i++)
b[i].wei+=k;
return l;
}
void z(void)
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<n;i++){
scanf("%d",&b[i].wei);
b[i].id=i+1;
}
while(m>=n){
f(0,n-1); m--;
printf("%d %d\n",b[n-1].id,k);
b[n-1].wei-=k;
}
if(m==n-1) solve(0,n-1);
else{
int p=find();
if(p==-1){
printf("-1\n");
return;
}
solve(0,p-1); solve(p,n-1);
}
return;
}
int main(void)
{
int T;
scanf("%d",&T);
while(T--) z();
return 0;
}