T1:
SDOI2009 最优图像image
题解:
正解为最大费用流,但是需要一步转化,最优方案要求乘积最大,对每个概率取对数后,可转化为求和最大,便于求解。
知道了这个我就华丽丽的。。。。T掉了
本题需要注意对算法进行优化。比如,由于费用为实数,且是1..99的对数值,因此可以对其乘以一个较大的整数,然后转为整数处理。
最后,采用反复求最短路+增广的算法会面临超时的风险,需要采用多次增广的方法进行优化。
像什么取对数,开根号之类容易挂精度的,可以先乘一个很大的数。
系统自带的有log,log2,log10,分别是以e,2,10为底,其中以2位底的会比另外两个快三倍,如果只是用来比大小,最后用log2。
注意设置常数写法,不要写加法= =
代码:
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 1e9
const int N=1e5;
int c[N];
int tot=-1,nxt[N],point[N],v[N],remind[N],last[N],dis[N],pip[105][105],d[N];
bool vis[N];
void addline(int x,int y,int cap,int cc)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=cap; c[tot]=cc;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; c[tot]=-cc;
}
int dfs(int now,int t,int limit)
{
vis[now]=1;
if(now==t||!limit) return limit;
int flow=0,f;
for (int i=point[now];i!=-1;i=nxt[i])
if (dis[v[i]]==dis[now]+c[i] && !vis[v[i]] && remind[i])//这里要这样写
{
f=dfs(v[i],t,min(remind[i],limit));//这样写要快很多
flow+=f;
limit-=f;
remind[i]-=f;
remind[i^1]+=f;
if(!limit) return flow;
}
return flow;
}
bool spfa(int s,int t)//找最长路
{
queue<int>q;
memset(dis,128,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(s);
dis[s]=0;
while (!q.empty())
{
int x=q.front(); q.pop(); vis[x]=0;
for (int i=point[x];i!=-1;i=nxt[i])
if (dis[v[i]]<dis[x]+c[i] && remind[i])
{
last[v[i]]=i;
dis[v[i]]=dis[x]+c[i];
if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
}
}
return dis[t]>0;
}
void zkw(int s,int t)//多路优化
{
while(spfa(s,t))
do
{
memset(vis,0,sizeof(vis));
dfs(s,t,INF);
}
while(vis[t]);
}
int main()
{
freopen("image.in","r",stdin);
freopen("image.out","w",stdout);
memset(point,-1,sizeof(point));
int n,m,x;
scanf("%d%d",&n,&m);
int s=0,t=n+m+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
pip[i][j]=tot+1;
scanf("%d",&x); x*=N; double s=log2(x); s*=N; long long d=s;
//精度优化
addline(i,j+n,1,d);
}
for (int i=1;i<=n;i++) scanf("%d",&x),addline(s,i,x,0);
for (int i=1;i<=m;i++) scanf("%d",&x),addline(i+n,t,x,0);
zkw(s,t);
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
if (remind[pip[i][j]]) printf("0");else printf("1");
printf("\n");
}
}
T2:
SDOI2009 学校食堂Dining
题解:
一看到这个题就弃疗了,暴力的话可以写个线段树╮(╯▽╰)╭
我们看到有
0≤Bi≤7
也就是说一个人的容忍不会超过7个人,这个东西一看就很好压
那么f[i][S][k]表示到第i个人为止,前面的人都选过了(i不一定),ta和ta后面的7个人还没选过压缩的集合为S,上一个选k号人的最优值
S我们可以用二进制表示,k我们只需要记录一个对i的相对位置就行,数值从-8到7
(上一次选择的人只有可能在[i-8,i+7]这个区间内,有i-8的原因是我可以在选i之前选ta,一样在忍耐程度内)
当我们循环到i的时候
- i选过了,没有出现在后面的S中,就将f [i, S, k]转移给f [i+1, S’, k-1],这里S’多了第i+8个人
- i没有选过,出现在后面的S中,则枚举S中的一个符合要求的人j作为k之后所取的那个人,即将状态f [i, S, k]转移给f [i, S-{j}, j],不要忘了加上花费
根据转移可以知道枚举时第一维必须从小到大枚举,第二维必须从大到小枚举
(从全都没选过开始)
计算贡献的一点点优化:(a or b)-(a and b)=a xor b
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 1e9
const int sum=(1<<8)-1;
int f[1005][260][20],t[1005],b[1005];
int main()
{
freopen("dining.in","r",stdin);
freopen("dining.out","w",stdout);
int T,n,hh;scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d%d",&t[i],&b[i]);
memset(f,0x7f,sizeof(f));
f[1][sum][-1+10]=0;
for (int i=1;i<=n;i++)
for (int j=sum;j>=0;j--)
for (int k=-8+10;k<=7+10;k++)
if (f[i][j][k]<INF)
{
if ((j&1)==0)//选过i,没有出现在后面的集合里
f[i+1][(j>>1)+(1<<7)][k-1]=min(f[i+1][(j>>1)+(1<<7)][k-1],f[i][j][k]);
else//没有选i的时候,我们可以取在ta后面合法的
{
int mn=INF;
for (int kk=0;kk<=7;kk++)
if (j&(1<<kk)) //没选过,可以选上
{
if (i+kk>mn) break;
mn=min(mn,i+kk+b[i+kk]);
//注意,合法的不仅仅要对于这一位i合法,更是后面的每一个
if (i+k-10==0) hh=0;else hh=t[i+kk]^t[i+k-10];
f[i][j-(1<<kk)][kk+10]=min(f[i][j-(1<<kk)][kk+10],f[i][j][k]+hh);
}
}
}
int ans=INF;
for (int i=-8;i<=7;i++) ans=min(ans,f[n+1][sum][i+10]);
printf("%d\n",ans);
}
}