1 dp
1.1最长上升子序列(O(nlogn)时间复杂度)
dp[i]=max(dp[k])+1;(0<=k<i,a[i]>a[k])
多加一个数组c[i],用于记录最长上升子序列长为i的序列中,最小的最后一个数字。这样查找max(dp[k])时只用找c[i]数组了,而c[i]数组递增,可以用二分查找查询。
链接:hdu 1025 Constructing Roads In JGShining's Kingdom 最长上升子序列(nlogn)
代码:
void LongestIncreasingSubsequence(int n,int tt)
{
int i,j,k;
c[1]=e[0].y;
t=1;
for(i=1;i<n;i++)
{
if(e[i].y<c[1])c[1]=e[i].y;
else if(e[i].y>c[t])c[++t]=e[i].y;
else
{
k=lower_bound(c+1,c+t+1,e[i].y)-c;
//c[k]=min(c[k],e[i].y);
c[k]=e[i].y;//lower_bound保证了e[i].y<=c[k]
}
}
if(t==1)printf("Case %d:\nMy king, at most %d road can be built.\n\n",tt,t);
else printf("Case %d:\nMy king, at most %d roads can be built.\n\n",tt,t);
}
1.2 约瑟夫环
标准的约瑟夫环是f[n]=(f[n-1]+k)%n。
变形:变成第一个删除的数是m。那么我们开始的位置就是m-k+1,最后剩余的数时(m-k+1+f[n])%n(可能为负的,需要变换回来)
其中k表示是要删除第k个,n表示总人数,编号从0~n-1。
2.凸包
int ConvexHull(Point *p,Point *ch,int n)//求凸包
{
sort(p,p+n);
int i,m=0,k;
for(i=0;i<n;i++)
{
while(m>1&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
ch[m++]=p[i];
}
k=m;
for(i=n-2;i>=0;i--)
{
while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
ch[m++]=p[i];
}
if(n>1)m--;
return m;
}
2.1.求凸包上最远点对
void solve(int m)//用旋转卡壳法求最长点对
{
if(m==2)
{
printf("%d\n",dis(ch[0],ch[1]));
return;
}
int i,j,k;
i=j=0;
for(k=0;k<m;k++)
{
if(ch[i].x>ch[k].x)i=k;
if(ch[j].x<ch[k].x)j=k;
}
int res=0,si=i,sj=j;
//printf("%d %d\n",i,j);
while(i!=sj||j!=si)
{
res=max(res,dis(ch[i],ch[j]));
if(Cross(ch[(i+1)%m]-ch[i],ch[(j+1)%m]-ch[j])<0)i=(i+1)%m;
else j=(j+1)%m;
}
printf("%d\n",res);
}
2.2.求两个凸包最近距离
void solve(Point *ch1,int t1,Point *ch2,int t2)
{
int i,j,k;
i=j=0;
for(k=0;k<t1;k++)
if(dcmp(ch1[k].x-ch1[i].x)<0)i=k;
else if(dcmp(ch1[k].x-ch1[i].x)==0&&dcmp(ch1[k].y-ch1[i].y)>0)i=k;
for(k=0;k<t2;k++)
if(dcmp(ch2[k].x-ch2[j].x)>0)j=k;
else if(dcmp(ch2[k].x-ch2[j].x)==0&&dcmp(ch2[k].y-ch2[j].y)<0)j=k;
int si=i,sj=j;
//printf("%d %d\n",i,j);
double ans=INF;
do
{
//printf("%d %d\n",i,j);
k=dcmp(Cross(ch1[(i+1)%t1]-ch1[i],ch2[(j+1)%t2]-ch2[j]));
if(k==0)
{
ans=min(ans,DisOfTwoSegment(ch1[i],ch1[(i+1)%t1],ch2[j],ch2[(j+1)%t2]));
i=(i+1)%t1;
j=(j+1)%t2;
//if(i==si||j==sj)break;
}
else if(k<0)
{
ans=min(ans,DistanceToSegment(ch2[j],ch1[i],ch1[(i+1)%t1]));
i=(i+1)%t1;
}
else
{
ans=min(ans,DistanceToSegment(ch1[i],ch2[j],ch2[(j+1)%t2]));
j=(j+1)%t2;
}
}while(i!=si||j!=sj);
printf("%.5f ",ans);
}
2.3判断两个凸包是否相交
//if(ConvexHullIntersection(ch1,t1,ch2,t2)&&ConvexHullIntersection(ch2,t2,ch1,t1))printf("YES\n");else printf("NO\n");
bool ConvexHullIntersection(Point *ch1,int t1,Point *ch2,int t2)//判断凸包是否相交,需要判两次
{
double angle[maxn],x;
int i,j,k,m;
if(t1==1)return true;
if(t1==2)
{
for(i=0;i<t2;i++)
{
k=dcmp(Cross(ch1[1]-ch1[0],ch2[i]-ch1[0]));
if(k==0&&Dot(ch1[1]-ch1[0],ch2[i]-ch1[0])>0)
{
if(Length(ch2[i]-ch1[0])<Length(ch1[1]-ch1[0]))break;
}
}
if(i<t2)return false;
if(t2==2&&SegmentProperIntersection(ch1[0],ch1[1],ch2[0],ch2[1]))return false;//两条线段
return true;
}
angle[0]=0;
for(i=2;i<t1;i++)
angle[i-1]=Angle(ch1[1]-ch1[0],ch1[i]-ch1[0]);
for(i=0;i<t2;i++)
{
j=dcmp(Cross(ch1[1]-ch1[0],ch2[i]-ch1[0]));
if(j<0||(j==0&&Dot(ch1[1]-ch1[0],ch2[i]-ch1[0])<0))continue;//除掉在凸包外以及凸包边线反向上的情况
j=dcmp(Cross(ch1[t1-1]-ch1[0],ch2[i]-ch1[0]));
if(j>0||(j==0&&Dot(ch1[t1-1]-ch1[0],ch2[i]-ch1[0])<0))continue;
x=Angle(ch1[1]-ch1[0],ch2[i]-ch1[0]);
m=lower_bound(angle,angle+t1-1,x)-angle;
if(m==0)j=0;
else j=m-1;
k=dcmp(Cross(ch1[j+1]-ch2[i],ch1[j+2]-ch2[i]));
if(k>=0)break;
}
if(i<t2)return false;
return true;
}
3.图论
3.1 最大匹配
增广路定理:用未盖点表示不与任何匹配边邻接的点,其他点位匹配点,即恰好和一条匹配边临界的点。从未盖点出发,依次经过非匹配边,匹配边,非匹配边,匹配边。。。所得到的路径称为交替路。注意,如果交替路的终点时一个未盖点,则称这条交替路位一条增广路。在增广路中,非匹配边比匹配边多一条。增广路的作用是改进匹配。如果有一条增广路,那么把此路上的匹配边和非匹配边互换,得到的匹配比刚才多一边。反过来,如果找不到增广路,则当前匹配就是最大匹配。
代码:
链接:hdu 1054 Strategic Game 最小顶点覆盖(二分图最大匹配)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include <vector>
#include<algorithm>
using namespace std;
const int maxn=1510;
int n;
int pre[maxn];//保存各点的匹配点
int vis[maxn];
vector<int> e[maxn];
int find(int u)//判断是否存在增广路,存在返回1
{
int i,v;
for(i=0;i<e[u].size();i++)
{
v=e[u][i];
if(vis[v])continue;
vis[v]=1;
if(pre[v]==-1||find(pre[v]))//找到未盖点,或者是增广路。
{
pre[v]=u;//匹配边和非匹配边交换
return 1;
}
}
return 0;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
int i,j,k,a,b,c,m;
memset(pre,-1,sizeof(pre));
for(i=0;i<n;i++)e[i].clear();
for(i=0;i<n;i++)
{
scanf("%d:(%d)",&a,&m);
for(j=0;j<m;j++)
{
scanf("%d",&b);
e[a].push_back(b);
e[b].push_back(a);
}
}
int ans=0;
for(i=0;i<n;i++)
{
memset(vis,0,sizeof(vis));
ans+=find(i);
}
printf("%d\n",ans/2);
}
return 0;
}
3.2 最小树形图/有向图的最小生成树
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
using namespace std;
const int INF=1<<30;
const int maxn=500+50;
const int maxm=2000+10;
struct edge{
int u,v,w;
edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}e[maxn+maxm];
int sum[maxn],a[maxn],tot,in[maxn],pre[maxn],id[maxn],vis[maxn];
void add(int u,int v,int w)
{
e[tot++]=edge(u,v,w);
}
int Directed_MST(int root,int numv,int nume)//建有向图的最小生成树,其所有边的权值和酒是答案,复杂度O(VE)
{
int i,j,k,u,v,ans=0;
while(true)
{
for(i=0;i<numv;i++)in[i]=INF;
for(i=0;i<nume;i++)
{
u=e[i].u;
v=e[i].v;
if(e[i].w<in[v]&&u!=v)
{
pre[v]=u;
in[v]=e[i].w;
}
}
for(i=0;i<numv;i++)
{
if(i==root)continue;
if(in[i]==INF)return -1;//无法成树
}
//找环,合成一个新的顶点
int t=0;
memset(id,-1,sizeof(id));
memset(vis,-1,sizeof(vis));
in[root]=0;
//标记每个环
for(i=0;i<numv;i++)
{
ans+=in[i];
v=i;
while(vis[v]!=i&&id[v]==-1&&v!=root)
{
vis[v]=i;
v=pre[v];
}
if(v!=root&&id[v]==-1)//存在环,标记相同的id
{
for(u=pre[v];u!=v;u=pre[u])
id[u]=t;
id[v]=t++;
}
}
if(t==0)break;//无环
for(i=0;i<numv;i++)
if(id[i]==-1)id[i]=t++;
//缩点,重新标记序号
for(i=0;i<nume;i++)
{
v=e[i].v;
e[i].u=id[e[i].u];
e[i].v=id[e[i].v];
if(e[i].u!=e[i].v)
e[i].w-=in[v];
}
numv=t;
root=id[root];
}
return ans;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)break;
int i,j,k;
sum[0]=tot=0;
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
a[i]++; //等级从1到a[i]开始
sum[i+1]=sum[i]+a[i];
}
//将所有等级作为一个节点,对于等级i,可以建一条对等级i-1的边,边权为0
//其中sum[n]为虚拟的跟,指向所有的课程的level0的点。
for(i=0;i<n;i++)
{
for(j=sum[i+1]-1;j>sum[i];j--)add(j,j-1,0);
add(sum[n],sum[i],0);
}
int c,d,l1,l2,money;
for(i=0;i<m;i++)
{
scanf("%d%d%d%d%d",&c,&l1,&d,&l2,&money);
add(sum[c-1]+l1,sum[d-1]+l2,money);
}
printf("%d\n",Directed_MST(sum[n],sum[n]+1,tot));
}
return 0;
}
4.数据结构
4.1 字典树
用树的形式像字典那样保存大量的字符串。
代码:
链接:uvalive 3942 Remember the Word 字典树+dp
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;
#define LL long long
const int maxn=4001*101;
const int maxm=3e5+10;
const int mod=20071027;
struct Trie{
int ch[maxn][26];
int val[maxn];
int sz;
Trie(){sz=1;memset(ch[0],0,sizeof(ch[0]));}
int idx(char c){return c-'a';}
void insert(char *s,int v)
{
int i,u=0,n=strlen(s);
for(i=0;i<n;i++)
{
int c=idx(s[i]);
if(!ch[u][c])
{
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
}
val[u]=v;
}
}e;
char str[maxm];
char a[101];
LL dp[maxm];
int main()
{
int tt=0;
while(scanf("%s",str)!=EOF)
{
int i,j,k,n,m,u;
scanf("%d",&n);
memset(e.ch[0],0,sizeof(e.ch[0]));
e.sz=1;
for(i=0;i<n;i++)
{
scanf("%s",a);
e.insert(a,1);
}
m=strlen(str);
memset(dp,0,sizeof(dp));
dp[m]=1;
for(i=m-1;i>=0;i--)
{
u=0;
for(j=i;j<m;j++)
{
int c=e.idx(str[j]);
if(!e.ch[u][c])break;
u=e.ch[u][c];
if(e.val[u])dp[i]+=dp[j+1];
}
dp[i]%=mod;
}
printf("Case %d: %lld\n",++tt,dp[0]);
}
return 0;
}
4.2 KMP算法
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;
const int maxn=1e5+10;
char a[maxn],b[maxn],c[30],d[30];
int f[maxn];
void getFail(char *P,int *f)//求失配函数
{
int m=strlen(P);
f[0]=f[1]=0;
for(int i=1;i<m;i++)
{
int j=f[i];
while(j&&P[i]!=P[j])j=f[j];
f[i+1]=P[i]==P[j]?j+1:0;
}
}
int find(char *T,char *P,int *f)//KMP算法
{
int n=strlen(T),m=strlen(P);
getFail(P,f);
int j=0;
for(int i=0;i<n;i++)
{
while(j&&P[j]!=T[i])j=f[j];
if(P[j]==T[i])j++;
//if(j==m)printf("%d\n",i-m+1);
}
return j;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s%s",c,a);
int i,j,k,n;
for(i=0;i<26;i++)
{
d[c[i]-'a']='a'+i;
}
n=strlen(a);
for(i=0;i<n;i++)b[i]=d[a[i]-'a'];
k=find(a+(n+1)/2,b,f);
//printf("%d\n",k);
for(i=0;i<n-k;i++)printf("%c",a[i]);
for(i=0;i<n-k;i++)printf("%c",b[i]);
printf("\n");
}
return 0;
}
4.3 manacher算法
回文分为两种:偶数长度和奇数长度。为了简便,我们可以将偶数长度的变成奇数长度的。怎么变,在各个字符之间加个不存在的符号,例如#。举例来说adda,可以变成#a#d#d#a#,即变为奇数的回文;同样的奇数长度的加#后还是奇数长度的最长回文。
接着我们定义dp[i]为以第i个字符为中心的回文半径。由于变成了奇数回文,所以回文半径=(回文长度+1)/2;又由于加了#,所以真实回文长度=改变后的回文半径-1。
我们枚举各个字符位置i,若存在一个i,使得i+dp[i]最大,我们用x=i,。然后分情况讨论:
1)i>x+dp[x]时,dp[i]=1,然后用while(a[i+dp[i]]==a[i-dp[i]])dp[i]++;向两边拓展。
2)i<=x+dp[x]时:由于x是回文,所以a[i]=a[2*i-x],之后就看dp[2*i-x]是否等于x+dp[x]-i了,也就是2*i-x的回文半径是否在x的回文半径内。若不相等,我们很容易证明dp[i]=min(dp[2*i-x],x+dp[x]-i);若相等则还需要用while向左右拓展。
在算法过程中,我们用maxv记录最长的回文半径即可。
注意:拓展的时候有可能越界,所以可以加越界判断,或者是两端字符加上不存在在的不同字符。
代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;
const int maxn=11e4+10;
char a[maxn*2];
int dp[maxn*2];
int Manacher(char *a,int *dp,int n)//求最大回文子串长度
{
int i,j,k,x=0,maxv=0;
for(i=2;i<2*n+1;i++)
{
if(x+dp[x]>i)dp[i]=min(dp[2*x-i],x+dp[x]-i);
else dp[i]=1;
while(a[i-dp[i]]==a[i+dp[i]])dp[i]++;
if(x+dp[x]<i+dp[i])x=i;
maxv=max(maxv,dp[i]);
}
return maxv-1;
}
int main()
{
while(scanf("%s",a)!=EOF)
{
int i,n;
n=strlen(a);
for(i=n;i>=0;i--)
{
a[2*i+2]=a[i];
a[2*i+1]='#';
}
a[0]='*';//a[2*n+2]='\0',防止在manacher算法的while循环中溢出
printf("%d\n",Manacher(a,dp,n));
}
}
5.数学
5.1 快速傅里叶变换/FFT
简单的来说就是求用nlogn的时间求得两个多项式乘积的系数。#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
#define maxn 400040
#define LL __int64
const double pi=atan(1.0)*4;
struct complex{//复数,重载+,-,*
double a,b;
complex(double aa=0,double bb=0){a=aa;b=bb;}
complex operator +(const complex &e){return complex(a+e.a,b+e.b);}
complex operator -(const complex &e){return complex(a-e.a,b-e.b);}
complex operator *(const complex &e){return complex(a*e.a-b*e.b,a*e.b+b*e.a);}
};
void change(complex y[],LL len)
{
LL i,j,k;
for(i=1,j=len/2;i<len-1;i++)
{
if(i<j)swap(y[i],y[j]);
k=len/2;
while(j>=k)
{
j-=k;
k/=2;
}
if(j<k)j+=k;
}
}
//FFT快速傅里叶变换的模板,用以将多项式系数转换成单位根(??应该是),这样得到的两个序列逐个相乘到得就是系数
//序列的卷积,即两个多项式乘积后的系数值
void fft(complex y[],LL len,LL on)
{
change(y,len);
LL i,j,k,h;
for(h=2;h<=len;h<<=1)
{
complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
for(j=0;j<len;j+=h)
{
complex w(1,0);
for(LL k=j;k<j+h/2;k++)
{
complex u=y[k];
complex t=w*y[k+h/2];
y[k]=u+t;
y[k+h/2]=u-t;
w=w*wn;
}
}
}
if(on==-1)
for(i=0;i<len;i++)
y[i].a/=len;
}
LL num[maxn],sum[maxn];
complex x[maxn];
LL a[maxn/4];
int main()
{
LL T;
scanf("%I64d",&T);
while(T--)
{
LL n,i,j,k,maxa=-1;
scanf("%I64d",&n);
memset(num,0,sizeof(num));
for(i=0;i<n;i++)
{
scanf("%I64d",&a[i]);
//存储个数,用以表示多项式的系数,其中未知数的指数表示长度,这样两个相同多项式乘积的系数表示取两根木棍
//其和是对应指数的组合个数(多项式相乘,系数相乘,指数相加)
num[a[i]]++;
maxa=max(maxa,a[i]);
}
sort(a,a+n);
LL len1,len=1,ans=0;
len1=maxa+1;
//两个多项式长n,m,那么乘积长n+m-1,
//这里求最接近且大于n+m-1的2^k,方便二叉树形式的运用吧
while(len<2*len1)len<<=1;
for(i=0;i<len1;i++)
x[i]=complex(num[i],0);
for(i=len1;i<len;i++)
x[i]=complex(0,0);
fft(x,len,1);//FFT转换成单位根形式
for(i=0;i<len;i++)
x[i]=x[i]*x[i];//卷积
fft(x,len,-1);//结果转换回来
for(i=0;i<len;i++)
num[i]=(LL)(x[i].a+0.5);
len=maxa*2;
//去掉取形同木棍
for(i=0;i<n;i++)
num[a[i]*2]--;
//去掉两根木棍左右交换的重复
for(i=1;i<=len;i++)
num[i]/=2;
sum[0]=0;
for(i=1;i<=len;i++)
sum[i]=sum[i-1]+num[i];
for(i=0;i<n;i++)
{
//两根木棍和大于a[i]的个数,三角形任意两边之和大于第三边,这里取的是较小两边,所以要减去大于a[i]的木棍
ans+=sum[len]-sum[a[i]];
//木棍,一根小于a[i],一根大于a[i](这里的大于,小于只是说在i的前后位置)
ans-=(n-1-i)*i;
//有根木棍取了自身的
ans-=n-1;
//两根木棍都大于a[i]
ans-=(n-1-i)*(n-2-i)/2;
}
LL t=n*(n-1)*(n-2)/6;
printf("%.7lf\n",(double)ans/t);
}
return 0;
}
6 精确覆盖问题和DLX算法
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
using namespace std;
const int maxn=10;
const int maxnode=100;
const int maxr=10;
struct DLX{
int n,sz; //列数,结点总数
int S[maxn]; //各列结点数
int row[maxnode],col[maxnode]; //各结点行列编号
int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表
int ansd,ans[maxr]; //解
void init(int n)
{
this->n=n;
//虚拟结点
for(int i=0;i<=n;i++)
{
U[i]=i;D[i]=i;L[i]=i-1;R[i]=i+1;
}
R[n]=0;L[0]=n;
sz=n+1;
memset(S,0,sizeof(S));
}
void addRow(int r,vector<int>columns)
{
int first=sz;
for(int i=0;i<columns.size();i++)
{
int c=columns[i];
L[sz]=sz-1;R[sz]=sz+1;D[sz]=c;U[sz]=U[c];
D[U[c]]=sz;U[c]=sz;
row[sz]=r;col[sz]=c;
S[c]++;sz++;
}
R[sz-1]=first;L[first]=sz-1;
}
//顺着链表A,遍历除s外的其他元素
#define FOR(i,A,s) for(int i=A[s];i!=s;i=A[i])
void remove(int c)
{
L[R[c]]=L[c];
R[L[c]]=R[c];
FOR(i,D,c)
FOR(j,R,i){U[D[j]]=U[j];D[U[j]]=D[j];--S[col[j]];}
}
void restore(int c)
{
FOR(i,U,c)
FOR(j,L,i){++S[col[j]];U[D[j]]=j;D[U[j]]=j;}
L[R[c]]=c;
R[L[c]]=c;
}
//d为递归深度
bool dfs(int d)
{
if(R[0]==0) //找到解
{
ansd=d; //记录解得长度
return true;<span style="white-space:pre"> </span>//只找到一个符合解,但不一定是最短的解
}
//找S最小的列c
int c=R[0]; //第一个为删除的列
FOR(i,R,0)if(S[i]<S[c])c=i;
remove(c); //删除第c列
FOR(i,D,c) //用结点i所在行覆盖第c列
{
ans[d]=row[i];
FOR(j,R,i)remove(col[j]); //删除结点i所在行能覆盖的所有其他列
if(dfs(d+1))return true;
FOR(j,L,i)restore(col[j]); //恢复结点i所在行能覆盖的所有其他列
}
restore(c); //恢复第c列
return false;
}
bool solve(vector<int>&v)
{
v.clear();
if(!dfs(0))return false;
for(int i=0;i<ansd;i++)v.push_back(ans[i]);
return true;
}
}dlx;
vector<int>row[maxr];
vector<int>v;
int main()
{
int r,n;
while(scanf("%d%d",&r,&n)!=EOF)
{
if(r==0&&n==0)break;
int i,j,k,m,x;
for(i=1;i<=r;i++)row[i].clear();
dlx.init(n);
for(i=1;i<=r;i++)
{
scanf("%d",&m);
for(j=0;j<m;j++)
{
scanf("%d",&x);
row[i].push_back(x);
}
dlx.addRow(i,row[i]);
}
if(!dlx.solve(v))printf("Fail.\n");
else
{
printf("Good.\n");
for(i=0;i<v.size();i++)
printf("%d ",v[i]);
printf("\n");
}
}
return 0;
}
/*
6 7
3 1 4 7
2 1 4
3 4 5 7
3 3 5 6
4 2 3 6 7
2 2 7
*/
2)精确覆盖(指针+DLX)
#include<cstdio>
#define N 505
#define M 1005
int m,n,H,cnt,size[M],ans;
struct Node
{
int r,c;
Node *U,*D,*L,*R;
}node[40005],row[N],col[M],head;
void init(int r,int c)
{
cnt=0;
head.r=r;
head.c=c;
head.L=head.R=head.U=head.D=&head;
for(int i=0;i<c;i++)
{
col[i].r=r;
col[i].c=i;
col[i].L=&head;
col[i].R=head.R;
col[i].U=col[i].D=col[i].L->R=col[i].R->L=&col[i];
size[i]=0;
}
for(int i=r-1;i>=0;i--)
{
row[i].r=i;
row[i].c=c;
row[i].U=&head;
row[i].D=head.D;
row[i].L=row[i].R=row[i].U->D=row[i].D->U=&row[i];
}
}
void insert(int r,int c)
{
Node *p=&node[cnt++];
p->r=r;
p->c=c;
p->R=&row[r];
p->L=row[r].L;
p->L->R=p->R->L=p;
p->U=&col[c];
p->D=col[c].D;
p->U->D=p->D->U=p;
++size[c];
}
void delLR(Node *p)
{
p->L->R=p->R;
p->R->L=p->L;
}
void delUD(Node *p)
{
p->U->D=p->D;
p->D->U=p->U;
}
void resumeLR(Node *p)
{p->L->R=p->R->L=p;}
void resumeUD(Node *p)
{p->U->D=p->D->U=p;}
void cover(int c)
{
if(c==H)
return;
Node *R,*C;
delLR(&col[c]);
for(C=col[c].D;C!=&col[c];C=C->D)
for(R=C->L;R!=C;R=R->L)
{
--size[R->c];
delUD(R);
}
}
void resume(int c)
{
if(c==H)
return;
Node *R,*C;
for(C=col[c].U;C!=&col[c];C=C->U)
for(R=C->R;R!=C;R=R->R)
{
++size[R->c];
resumeUD(R);
}
resumeLR(&col[c]);
}
void dfs(int k)
{
if(head.L==&head)
{
if(k<ans)
ans=k;
return;
}
if(k>=ans)
return;
int INF=-1u>>1,c=-1;
Node *p,*rc;
for(p=head.L;p!=&head;p=p->L)
if(size[p->c]<INF)
INF=size[c=p->c];
if(!INF)
return;
cover(c);
for(p=col[c].D;p!=&col[c];p=p->D)
{
for(rc=p->L;rc!=p;rc=rc->L)
cover(rc->c);
dfs(k+1);
for(rc=p->R;rc!=p;rc=rc->R)
resume(rc->c);
}
resume(c);
}
int main()
{
int t,p;
scanf("%d",&t);
while(t--)
{
int i,j,k,x0,x1,y0,y1;
scanf("%d%d%d",&n,&m,&p);
init(p+1,H=n*m);
for(k=0;k<p;k++)
{
scanf("%d%d%d%d",&x0,&y0,&x1,&y1);
for(i=x0;i<x1;i++)
for(j=y0;j<y1;j++)
insert(k,i*m+j);
}
ans=999;
dfs(0);
if(ans<999)
printf("%d\n",ans);
else
puts("-1");
}
}
3)多次覆盖(DLX+A*)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
#include <ctime>
using namespace std;
const int maxn=100;
const int maxnode=1000;
const int maxr=100;
const int INF=1e9;
struct DLX{
int n,sz; //列数,结点总数
int S[maxn]; //各列结点数
int row[maxnode],col[maxnode]; //各结点行列编号
int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表
int ansd,ans[maxr]; //解
int vis[maxn]; //A*算法时标记求过的列
void init(int n)
{
this->n=n;
//虚拟结点
for(int i=0;i<=n;i++)
{
U[i]=i;D[i]=i;L[i]=i-1;R[i]=i+1;
}
R[n]=0;L[0]=n;
sz=n+1;
memset(S,0,sizeof(S));
ansd=INF;
}
void addRow(int r,vector<int>columns)
{
int first=sz;
for(int i=0;i<columns.size();i++)
{
int c=columns[i];
L[sz]=sz-1;R[sz]=sz+1;D[sz]=c;U[sz]=U[c];
D[U[c]]=sz;U[c]=sz;
row[sz]=r;col[sz]=c;
S[c]++;sz++;
}
R[sz-1]=first;L[first]=sz-1;
}
//顺着链表A,遍历除s外的其他元素
#define FOR(i,A,s) for(int i=A[s];i!=s;i=A[i])
void remove(int c)
{
FOR(i,D,c)
{
L[R[i]]=L[i];
R[L[i]]=R[i];
}
}
void restore(int c)
{
FOR(i,U,c)
{
L[R[i]]=i;
R[L[i]]=i;
}
}
int A()//A*思想,求出要找到结果至少还要选择的行数
{
int i,j,k,res=0;
memset(vis,0,sizeof(vis));
FOR(i,R,0)
{
if(!vis[i])
{
res++;
vis[i]=1;
FOR(j,D,i)
FOR(k,R,j)
vis[col[k]]=1;
}
}
return res;
}
//d为递归深度
void dfs(int d)
{
if(R[0]==0) //找到解
{
ansd=min(ansd,d); //记录解得长度
return ;
}
if(d+A()>=ansd)return ;
//找S最小的列c
int c=R[0]; //第一个为删除的列
FOR(i,R,0)if(S[i]<S[c])c=i;
//remove(c); //删除第c列
FOR(i,D,c) //用结点i所在行覆盖第c列
{
remove(i);
FOR(j,R,i)remove(j); //删除结点i所在行能覆盖的所有其他列
dfs(d+1);
FOR(j,L,i)restore(j); //恢复结点i所在行能覆盖的所有其他列
restore(i);
}
//restore(c); //恢复第c列
}
int solve()
{
dfs(0);
if(ansd==INF)return 0;
return ansd;
}
}dlx;
vector<int>row[maxr];
vector<int>v;
int c[10][10];
int vis[1025];
int bitcount[1025];
int get(int x)
{
int ans=0;
while(x)
{
ans+=(x&1);
x=x>>1;
}
return ans;
}
void init()
{
int i,j,k;
memset(c,0,sizeof(c));
c[0][0]=1;
for(i=1;i<=8;i++)
{
c[i][0]=1;
for(j=1;j<=i;j++)
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
for(i=0;i<(1<<8);i++)
{
bitcount[i]=get(i);
}
}
int main()
{
freopen("D:\\in.txt","r",stdin);
freopen("D:\\out.txt","w",stdout);
double a = clock();
init();
int n,m,r;
while(scanf("%d%d%d",&n,&m,&r)!=EOF)
{
int i,j,k,t=0;
dlx.init(c[n][r]);
memset(vis,0,sizeof(vis));
for(i=1,k=0;i<(1<<n);i++)
{
if(bitcount[i]==r)vis[i]=++k;
}
for(i=1;i<=c[n][m];i++)
{
row[i].clear();
}
for(i=1;i<(1<<n);i++)
{
if(bitcount[i]==m)
{
t++;
for(j=i;j;j=(j-1)&i)
{
if(bitcount[j]==r)
row[t].push_back(vis[j]);
}
dlx.addRow(t,row[t]);
}
}
printf("%d %d %d:%d\n",n,m,r,dlx.solve());
}
double b = clock();
printf("%lf\n", (b - a) / CLOCKS_PER_SEC); //运行时间
return 0;
}
7