背景:
假的深蓝题。
不过要疯狂调参(差不多
1
1
1版)。
看运气。
题目传送门:
https://www.luogu.org/problemnew/show/P3936
题意:
n
∗
m
n*m
n∗m的矩阵,现在有
c
c
c种颜色
a
i
a_i
ai,保证
∑
i
=
1
c
a
i
=
n
∗
m
\sum_{i=1}^{c}a_i=n*m
∑i=1cai=n∗m,要将这些颜色全部填入矩阵中,求联通块之间的边界的长度和。
思路:
已经是一种套路了。
还是先随机放颜色,模拟退火中随机选两个数交换颜色,
Θ
(
1
)
\Theta(1)
Θ(1)算贡献即可(因为只有这两个位置四周的贡献会有更改)。
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<ctime>
#define eps 1e-16
using namespace std;
int n,m,c;
int a[100],b[100],belong[100][100],sum[100],d[100][100];
int CALC()
{
int tot=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
tot+=(i<n&&belong[i][j]!=belong[i+1][j])+(j<n&&belong[i][j]!=belong[i][j+1]);
return tot;
}
int calc(int x,int y)
{
return (x<n&&belong[x][y]!=belong[x+1][y])+(x>1&&belong[x][y]!=belong[x-1][y])
+(y<m&&belong[x][y]!=belong[x][y+1])+(y>1&&belong[x][y]!=belong[x][y-1]);
}
void give()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
d[i][j]=belong[i][j];
}
void fire()
{
memset(sum,0,sizeof(sum));
for(int i=1;i<=c;i++)
b[i]=a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
belong[i][j]=rand()%c+1;
while(!b[belong[i][j]]) belong[i][j]=rand()%c+1;
b[belong[i][j]]--;
}
int ans=CALC();
double t=2000+rand()%1000;
while(t>eps)
{
int x1=rand()%n+1,y1=rand()%m+1,x2=rand()%n+1,y2=rand()%m+1;
int now=ans-calc(x1,y1)-calc(x2,y2);
swap(belong[x1][y1],belong[x2][y2]);
now+=calc(x1,y1)+calc(x2,y2);
int delta=now-ans;
if(delta<0||exp(-(double)delta/t)*RAND_MAX>rand()) ans=now,give(); else swap(belong[x1][y1],belong[x2][y2]);
t*=0.999991;
}
}
int main()
{
srand(time(0));
scanf("%d %d %d",&n,&m,&c);
for(int i=1;i<=c;i++)
scanf("%d",&a[i]);
fire();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
printf("%d ",d[i][j]);
printf("\n");
}
}