【codechef除草】

23 篇文章 0 订阅
21 篇文章 0 订阅

Lighting the shop

给定一个 N*M 的方格,其中有些格子被标记。你要将被标记的格子染色,使得颜色数最少,并且保证同行同列没有相同颜色的格子。

首先这个矩阵填色可以化归为二分图的边染色,将每行每列看作一个节点,分为两部,所谓限制无非是同一个点连出去的边不能同色, 这就是一个最小边染色的问题, 如同其他很多图上的 np 问题,这在二分图上是有多项式算法的。
我们来看最少要用多少颜色,设最大点度为 d,那么显然的,ans>=d,接着我们将图补为 d-正则图, 可知此图可做 d 次完备匹配, 而每次完备匹配的边都是互相独立的, 可用同一颜色,因此最多只要用 d 种颜色即可,ans 也达到了下界。

考虑我们拿到了两个点 x,y,分别取 x 未取的颜色为 i,y 未取的颜色为 j,倘若 i==j,直接染即可,否则我们进行调整。
现在我们只考虑颜色为 i 与 j 的边所构出的子图,从点 y 我们可以找出这样一种路径,i 和 j交替存在,要明确的一点是

这两种路径都不存在环,因为在这之前的图都是符合要求的。 然后,我们将这条路径反色,显然也是符合要求的,然后原来的点 y 就可以用 i 这个颜色了

综上,匈牙利变种。 。 。 

比较猥琐的是,虽然常数很小,但是你不贪心初始化的话还是会超时。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,e,ans,du[1000];
char ch[1000][1000];
int b[2000][1000],c[1000][1000],d[1000][1000];
void addedge(int x,int y) {c[x][++c[x][0]]=y,du[x]++,du[y]++;}
void edmonds(int x,int c1,int c2)
{
  if (!b[x][c1]) {
    b[x][c1]=b[x][c2],b[x][c2]=0;
    return ;
  }
  edmonds(b[x][c1],c2,c1);
  e=b[x][c2],b[x][c2]=b[x][c1],b[x][c1]=e;
}
void init()
{
  int i,j,l,k,p;
  scanf("%d\n",&n);m=n;
  memset(b,0,sizeof(b));
  memset(d,0,sizeof(d));
  memset(du,0,sizeof(du));
  for (i=1;i<=n;i++) c[i][0]=0;
  for (i=1;i<=n;i++) {
    scanf("%s\n",ch[i]+1);
    for (j=1;j<=m;j++) 
      if (ch[i][j]=='*') addedge(i,j+n);
  }
  ans=0;
  for (i=1;i<=n+m;i++) ans=max(ans,du[i]);
  for (i=1;i<=min(ans,n);i++)
    for (j=1;j<=min(m,ans);j++) 
    if (ch[i][j]=='*') {
      k=(i+j-1)%ans;
      if (!k) k=ans;
      b[i][k]=j+n,b[j+n][k]=i;
    }
  for (i=1;i<=n;i++) {
    for (p=1;p<=ans && b[i][p];p++) ;
    for (j=1;j<=c[i][0];j++) {
      k=c[i][j];
      if (i<=ans && k-n<=ans) continue;
      for (;p<=ans && b[i][p];p++) ;
      for (l=1;l<=ans;l++) {
        if (b[k][l]) continue;
        edmonds(k,p,l);
        b[i][p]=k,b[k][p]=i;
        break;
      }
    }
  }
  for (i=1;i<=n;i++)
    for (j=1;j<=ans;j++)
      d[i][b[i][j]-n]=j;
  //  printf("%d %d %d\n",ans,n,m);
  printf("%d\n",ans);
  for (i=1;i<=n;i++) {
    for (j=1;j<=m;j++) printf("%d ",d[i][j]);
    printf("\n");
  }
}
int main()
{
  freopen("i.txt","r",stdin);
  freopen("o.txt","w",stdout);
  int t=0;
  scanf("%d\n",&t);
  for (;t;t--) init();
  return 0;
}

The Reversed World

给定长度均为 N 的数字(可以有前导的零)A、B、C。问是否可以把 C 中的数码重新排列(也可以和原来一样) ,使得新的 C 满足:C>A 且 C>B 且 rev(C) < rev(A) 且 rev(C) < rev(B)。
其中,rev(X)表示将 X 反过来,比如 rev(12340) = 04321
如果有解,输出最小的一个。

贪心的想,我们直接999...555...111,就达到了正着最大,反着最小,因此我们在某位填某个数字可不可行就判断后面取上界是否符合即可,这样就可以从头贪心至尾

这样子复杂度就是n*10*(判断复杂度),我们的任务就是尽可能降低判断复杂度,可以看出后面取上界的话,数字变化的点只有10个,所以o(10)的判断一下就可以比较大小(外加已知部分的大小状态),说起来不是很复杂,细节和特殊情况还是很讨厌。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
char a[1000001],b[1000001],c[1000001],A[1000001],B[1000001];
int flag,sign,n,f[1000001],g[1000001],v[1000001],t,tmp;
void origin()
{
  int i;
  for (i=1;i<=n;i++) 
    if (a[i]==b[i]) A[i]=a[i];
    else if (a[i]>b[i])
      for (;i<=n;i++) A[i]=a[i];
    else for (;i<=n;i++) A[i]=b[i];
  for (i=n;i>=1;i--)
    if (a[i]==b[i]) B[i]=b[i];
    else if (a[i]<b[i]) 
      for (;i>=1;i--) B[i]=a[i];
    else for (;i>=1;i--) B[i]=b[i];
  for (i=n-1,f[n]=n;i>=1;i--)
    if (A[i]==A[i+1]) f[i]=f[i+1];else f[i]=i;
  for (i=2,g[1]=1;i<=n;i++)
    if (B[i]==B[i-1]) g[i]=g[i-1];else g[i]=i;
  memset(v,0,sizeof(v));
  for (i=1;i<=n;i++) v[c[i]]++;
}
int check(int x,int y)
{
  int i,k;
  if (!flag && y<=A[x]) {
    k=x;
    for (i='9';i>='0';k+=v[i],i--) 
      if (!v[i]) continue;
      else if (i>A[k+1]) break;
      else if (i<A[k+1]) return 0;
      else if (k+v[i]>f[k+1]) {
        if (i>A[f[k+1]+1]) break;
        return 0;
      }
      else if (k+v[i]<f[k+1]) return 0;
    if (i<'0') return 0;
  }
  k=n;
  for (i='0';i<='9';k-=v[i],i++) 
    if (!v[i]) continue;
    else if (i<B[k]) break;
    else if (i>B[k]) return 0;
    else if (k-v[i]+1<g[k]) {
      if (i<B[g[k]-1]) break;
      return 0;
    }
    else if (k-v[i]+1>max(g[k],x+1)) return 0;
  if (i<='9') return 1;
  if (y<B[x]) return 1;else if (y>B[x]) return 0;
  return sign;
}
int main()
{
  freopen("REVERSED.in","r",stdin);
  freopen("REVERSED.out","w",stdout);
  int i,j;
  scanf("%d\n",&t);
  for (;t;t--) {
    scanf("%d\n",&n);
    scanf("%s\n%s\n%s\n\n",a+1,b+1,c+1);
    origin();
    for (flag=0,sign=0,i=1;i<=n;i++) {
      if (!flag) j=A[i];else j='0';
      if (i==667)
        int k=1;
      for (;j<='9';j++) 
	      if (v[j] && (v[j]--,tmp=check(i,j),v[j]++,tmp)) break;
      if (j>'9') break;
      c[i]=j;v[c[i]]--;
      if (c[i]<B[i]) sign=1;else if (c[i]>B[i]) sign=0;
      if (!flag && c[i]!=A[i]) flag=1;
    }
    if (i<=n) printf("-1\n",i,c+1);
    else printf("%s\n",c+1);
  }
  return 0;
}
Travelling Salesman Again !

现在有 N 个结点的完全图,你需要找一个总费用最小的哈密尔顿回路。计费用方式如下:有 M 个收费站,每个收费站有参数 Xi和 Yi,当你从 I 走到J 时,所有满足 I≤Xi 且 Yi≤J 的收费站将索取 1 的费用。

这种计算方式很奇葩,所以有个很奇怪的结论,最优路径必定是1...n...1,就变成一般的dp了

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
const int oo=1073741819;
using namespace std;
int ev[1010][1010],f[1010][1010],i,j,ans[1010][1010],n,k,x,y,v[1010],t,sum;
void init()
{
  scanf("%d%d\n",&n,&k);
  memset(ev,0,sizeof(ev));
  for (i=1;i<=k;i++) {
    scanf("%d%d\n",&x,&y);x++,y++;
    for (j=1;j<=x;j++) ev[j][y]++;
  }
  memset(f,0,sizeof(f));
  for (i=1;i<=n;i++) {
    f[i][0]=ev[i][0];
    for (j=1;j<=n;j++) {
      f[i][j]=f[i][j-1]+ev[i][j];
      //           printf("%d ",f[i][j]);
    }
       // printf("\n");
  }
  //  memset(l,0,sizeof(l));memset(r,0,sizeof(r));
  memset(ans,127,sizeof(ans));
  ans[1][1]=0;
  for (i=1;i<=n;i++) 
    for (j=0;j<=i-1;j++) {
      ans[i][j]=min(ans[i][j],ans[i-1][j]+f[i-1][i]);
      ans[j][i]=min(ans[j][i],ans[j][i-1]+f[i][i-1]);
      ans[i][i-1]=min(ans[i][i-1],ans[j][i-1]+f[j][i]);
      ans[i-1][i]=min(ans[i-1][i],ans[i-1][j]+f[i][j]);
    }
  sum=min(ans[n][0]+f[n][1],ans[0][n]+f[1][n]);
  for (i=1;i<=n-1;i++) {
    sum=min(sum,ans[n][i]+f[n][i]);
    sum=min(sum,ans[i][n]+f[i][n]);
  }
  printf("%d\n",sum);
}
int main()
{
  freopen("input.txt","r",stdin);
  freopen("output.txt","w",stdout);
  scanf("%d\n",&t);
  for (;t;t--) init();
  return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值