D2欧拉路,拓扑排序,和差分约束
第一题:太鼓达人;BZOJ3033
题意:给出k,求一个最长的M位01串,使其从每一个位置向后走k个得到 的M个k位01串互不相同(最后一个和第一个相邻,即是一个环)。输出 字典序最小的答案。 2 ≤ k ≤ 11。
结论+爆搜;
第二问我们将每个k二进制数看成一个点,在它后面加上0/1就能得 到两个新数,我们从这个数向两个新数连边,于是这就变成了求一个欧 拉回路的问题。显然此题是存在欧拉回路的第一问答案为2的k次方 ,第二问暴力求欧拉回路即可。
发现sjh的注释很详细,所以贿赂搞来了一个满满注释的代码,可以更好地理解;
#include<bits/stdc++.h> using namespace std; const int maxn=2e3+50; template<typename T>inline void read(T &x) { x=0; T f=1,ch=getchar(); while (!isdigit(ch) && ch^'-') ch=getchar(); if (ch=='-') f=-1, ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } int n,t,ans[maxn]; bool used[maxn]; inline bool eular(int x,int k)//x是当前子串的十进制表示,k是当前查询次 { if (used[x]) return 0; used[x]=1,ans[k]=x&1;//统计答案,&的规则是:有零则零,否则为一,返回末位数字 if (k==t) return 1;//查询结束 if (eular((x<<1)&(t-1),k+1)) return 1; if (eular((x<<1|1)&(t-1),k+1)) return 1;//将每个二进制数看成一个点,将他前面的数删去,并在它后面加上0/1就能得到两个新数 used[x]=0;//回溯,以免影响之后的dfs return 0; } int main() { read(n); t=1<<n; printf("%d ",t); eular(t-2,1);//从每位走n位子串,可得到的完整子串数量为2^n-2 for (int i=1;i<=t;++i) printf("%d",ans[i]); return 0; }
意外发现邱神的代码,但没有写题解,所以只能看一下
#include<bits/stdc++.h> using namespace std; int read() { int a; cin>>a; return a; } int i,k,m; struct node { int x,deep; }o[10010]; bool Orz(node a,node b) { return a.deep<b.deep; } void dfs(int now,int deep) { if(deep==m+1) { now=now%(m/2); now=now*2; if(now) return ; sort(o,o+m,Orz); for(i=1;i<k;i++) cout<<0; for(i=0;i<=m-k;i++) cout<<o[i].x%2; exit(0) ; } //这里应该有一个关于deep和now的if使得dfs停下来并输出 now=now%(m/2); now=now*2; if(!o[now].deep) { o[now].deep=deep; dfs(now,deep+1); o[now].deep=0; } now++; if(!o[now].deep) { o[now].deep=deep; dfs(now,deep+1); o[now].deep=0; } return ; } int main() { //freopen("123.in","r",stdin); k=read();m=int(pow(2,k*1.0)); cout<<m<<' '; for(i=0;i<m;i++) o[i].x=i; o[0].deep=1; dfs(0,2); }
第二题;poj1386
我们不必要知道每个单词的每个字母,只需要记录首字母和尾字母,所以转换成了一个判断欧拉回路的题目;
首先我们需要知道两个字母知否在一个同一个联通块上,我们可以用并差集来写,然后判断欧拉回路,方法不再陈述;
#include<algorithm> #include<bitset> #include<cctype> #include<cerrno> #include<clocale> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque> #include<exception> #include<fstream> #include<functional> #include<limits> #include<list> #include<map> #include<iomanip> #include<ios> #include<iosfwd> #include<iostream> #include<istream> #include<ostream> #include<queue> #include<set> #include<sstream> #include<stack> #include<stdexcept> #include<streambuf> #include<string> #include<utility> #include<vector> #include<cwchar> #include<cwctype> #define MAX 26 using namespace std; int out[MAX],in[MAX],flag[MAX],p[MAX],father[MAX]; int Find(int x) { if(father[x]!=x) father[x]=Find(father[x]); return father[x]; } void Union(int a,int b) { int p=Find(a); int q=Find(b); if(p!=q) father[q]=p; } int main() { int i,k,count,a,b,nc,n; char str[1002]; cin>>nc; while(nc--) { for(i=0;i<26;i++) father[i]=i; memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(flag,0,sizeof(flag)); cin>>n; for(i=1;i<=n;i++) { cin>>str; a=str[0]-'a'; b=str[strlen(str)-1]-'a'; Union(a,b); in[b]++; out[a]++; flag[a]=flag[b]=1; } count=0; for(i=0;i<26;i++) { father[i]=Find(i); if(flag[i] && father[i]==i) count++; } if(count>1) { cout<<"The door cannot be opened."<<endl; continue; } k=0; for(i=0;i<26;i++) { if(flag[i] && in[i]!=out[i]) p[k++]=i; } if(k==0) { cout<<"Ordering is possible."<<endl; continue; } if(k==2 && ((out[p[0]]-in[p[0]]==1 && in[p[1]]-out[p[1]]==1) || (out[p[1]]-in[p[1]]==1 && in[p[0]]-out[p[0]]==1)) ) { cout<<"Ordering is possible."<<endl; continue; } cout<<"The door cannot be opened."<<endl; } return 0; }
第三题:CF516/B
题目大意:给一个n × m的矩阵,问在其中空白处放置1 × 2, 2 × 1的骨牌的方案 是否存在且唯一。 1 ≤ n, m ≤ 1000;
我们可以用度来表示这个节点当前周围放置的个数;
那么对应度为1的点是一定确定了放置这个点的方式的(显然),对应这个点的四周四个位子中,此时一定只有一个点,那么对应将此处和那个位子直接放置这个1 × 2的形状即可。然后处理完这个点之后,对其他点进行操作,如果再发现了度 为1的点,入队。一直处理下去这样的类似拓扑排序的过程即可。 如果最后还剩下了,说明无法完全覆盖,或者方案不止一个;
所以这个题是拓扑排序的一个拓展;
#include <iostream> #include <cstdio> #include <cstdlib> #include <queue> using namespace std; typedef struct Point { int x,y; }Point; Point a[4000000]; int in[2002][2002]; char p[2002][2002]; int dx[] = { 0, 0, 1,-1}; int dy[] = { 1,-1, 0, 0}; char ch[] = "><v^"; char st[] = "<>^v"; int m,n; int k; bool check(int x,int y) {return x > 0 && x <= m && y > 0 && y <= n;} void ans(int x,int y) { int i,xx,yy,cnt = 0; for(i = 0; i < 4; ++i) { xx = x+dx[i]; yy = y+dy[i]; if(check(xx,yy)&&p[xx][yy]=='.') cnt++; } in[x][y]=cnt; } bool topo() { int i,j,x,y,xx,yy,xxx,yyy,cnt = 0; queue <pair<int,int> > q; for(j = 0; j <k; ++j) { x = a[j].x; y = a[j].y; if(in[x][y] == 1) { q.push(pair<int,int> (x,y)); in[x][y] = 0; } } pair <int,int> m; while(!q.empty()) { m = q.front(); q.pop(); x = m.first; y = m.second; in[x][y]--; for(j = 0; j < 4; ++j) { xx = x+dx[j]; yy = y+dy[j]; if(check(xx,yy) && p[xx][yy] == '.') { in[xx][yy] = 0; p[xx][yy] = ch[j]; p[x][y] = st[j]; for(int f = 0; f < 4; ++f) { xxx = xx+dx[f]; yyy = yy+dy[f]; if(check(xxx,yyy) && p[xxx][yyy] == '.') { ans(xxx,yyy); if(in[xxx][yyy] == 1) q.push(pair<int,int>(xxx,yyy)); } } cnt+=2; } } } return cnt == k; } int main() { int i,j; scanf("%d %d",&m,&n); k = 0; for(i = 1; i <= m; ++i) { scanf("%s",p[i]+1); for(j = 1; j <= n; ++j) if(p[i][j] == '.') { a[k].x = i; a[k++].y = j; in[i][j] = 0; if(check(i-1,j) && p[i-1][j] == '.') { in[i-1][j]++; in[i][j]++; } if(check(i,j-1) && p[i][j-1] == '.') { in[i][j-1]++; in[i][j]++; } } } if(topo()) { for(i = 1; i <= m; ++i) printf("%s\n",p[i]+1); }else puts("Not unique"); return 0; }
第四题:poj3169
题目大意:有n头牛,他们按顺序排成了一排,有些牛关系比较好,他们的距 离不能超过某个距离,还有些牛关系不好,他们之间的距离不能小于某 个距离,可能会有多头牛挤在同一位置上,问1号牛和n号牛之间的最大 距离是多少,如果不存在满足条件的排列则输出−1,如果距离无限大则 输出−2。 2 ≤ n ≤ 1000。
查分约束版子题,由题意列出不等式根据三角不等式,建图,跑最短路即可;
题解:令d[i]表示第i头牛的位置,因为牛按顺序排列,则有d[i] ≤ d[i + 1], 即d[i] − d[i + 1] ≤ 0。 关系不好的牛有d[a] + d ≤ d[b]。 关系好的牛有d[a] + D ≤ d[b], 即d[a] − d[n] ≤ −D 。 跑最短路即可。
#include<algorithm> #include<bitset> #include<cctype> #include<cerrno> #include<clocale> #include<cmath> #include<complex> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<deque> #include<exception> #include<fstream> #include<functional> #include<limits> #include<list> #include<map> #include<iomanip> #include<ios> #include<iosfwd> #include<iostream> #include<istream> #include<ostream> #include<queue> #include<set> #include<sstream> #include<stack> #include<stdexcept> #include<streambuf> #include<string> #include<utility> #include<vector> #include<cwchar> #include<cwctype> const int MAX=1e6+5; const int inf=0x3f3f3f3f; using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,x,y,vis[MAX*2],dis[MAX*2],lin[MAX*2],tot,ans[MAX*2],a,b,c; struct gg { int y,next,v; }an[MAX]; void init(int x,int y,int z) { an[++tot].y=y; an[tot].v=z; an[tot].next=lin[x]; lin[x]=tot; } queue<int> q; int spfa(int s,int n) { for (int i=0;i<MAX;++i) dis[i]=inf; memset(ans,0,sizeof(ans)); memset(vis,0,sizeof(vis)); ++ans[s]; dis[s]=0; vis[s]=1; q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; for (int i=lin[x];i;i=an[i].next) { int y=an[i].y; if (dis[y]>dis[x]+an[i].v) { dis[y]=dis[x]+an[i].v; if (!vis[y]) { vis[y]=1,q.push(y); ++ans[y]; if (ans[y]>n) return -1; } } } } if(dis[n]==inf) return -2; return dis[n]; } int main() { int n=read(),x=read(),y=read(); for (int i=1;i<n;++i) init(i+1,i,0); for (int i=1;i<=x;++i) { int a=read(),b=read(),d=read(); init(a,b,d); } for (int i=1;i<=y;++i) { int a=read(),b=read(),d=read(); init(b,a,-d); } cout<<spfa(1,n)<<endl; return 0; }
第五题:poj2983
题目大意:银河系中有n个防御站排成一条直线,给定m条信息,每条信息的 格式如下: P A B X代表A站在B站北方X光年处。 V A B 代表A站在B站北方,而且距离至少为1光年。 问是否存在这样的一个防御战排列满足上述要求,是输出Reliable,否输 出Unreliable。
dist[A] − dist[B] ≥ X,表示A在B北边至少X光年位置。变形为: dist[B] <= dist[A] − X;所以A指向B有条权值为-X的边。 对于A − B = X,建两条边dist[B] ≤ dist[A] − X, dist[A] ≤ dist[B] + X。
SPFA存在负权环则说明不符合实际情况。
#include <iostream> #include <cstring> #include <string> #include <vector> #include <queue> #include <cstdio> #include <set> #include <math.h> #include <algorithm> #include <queue> #include <iomanip> #include <map> #include <cctype> #define INF 0x3f3f3f3f #define MAXN 1000005 #define Mod 1000000007 using namespace std; struct Edge { int v,w,next; }; Edge edge[MAXN<<1]; int head[MAXN],n,m,e,vis[MAXN],dis[MAXN]; int p[MAXN]; void add(int u,int v,int w) { edge[e].v=v; edge[e].w=w; edge[e].next=head[u]; head[u]=e; e++; } bool spfa(int u) { memset(vis,0,sizeof(vis)); for(int i=1;i<=n;++i) dis[i]=-INF; dis[u]=0; queue<int> q; q.push(u); while(!q.empty()) { u=q.front(); q.pop(); vis[u]=0; p[u]++; if(p[u]>n) return false; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v,w=edge[i].w; if(w+dis[u]>dis[v]) { dis[v]=w+dis[u]; if(!vis[v]) { vis[v]=1; q.push(v); } } } } return true; } int main() { while(~scanf("%d%d",&n,&m)) { e=0; memset(head,-1,sizeof(head)); memset(p,0,sizeof(p)); for(int i=0;i<m;++i) { int a,b,c; char op; cin>>op; if(op=='P') { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,-c); } else { scanf("%d%d",&a,&b); add(a,b,1); } } for(int i=1;i<=n;++i) add(0,i,0); if(spfa(0)) printf("Reliable\n"); else printf("Unreliable\n"); } return 0; }
第六题:hdu3440
题目大意:T组数据,有n栋房子在一条直线上,给出每栋房子的高度和开始时 的相对位置,可以移动一些房子,但不能改变这些房子的相对位置,现 在从最矮的房子开始,每次跳至第一个比它高的房子,经过n − 1跳到达 最高的房子。而且每次跳跃的水平距离最大是D,房子不能在同一位置, 只能在整点位置。问最矮的房子和最高的房子之间的最大距离可以是多 少?如果不能从最矮的房子到达最高的房子则输出−1。 1 ≤ T ≤ 500, 1 ≤ N ≤ 500。
令d[i]表示第i栋房子与第一栋房子之间的最大距离,那么我们要求的就是的d[n]。 首先每栋房子之间的相对位置已经确定且不能在同一位置,那么d[i + 1]>d[i],即d[i + 1] − d[i] ≥ 1,即d[i] − d[i + 1] ≤ −1。 还有一个条件是相邻两个要跳跃的距离不可以大于D,可以写出另一个方程。有一点小问题就是建边的方向和跳跃的方向不同,我们要保证是向一个方向建图的。 若从左到右建边,如果1在n的左面就从1到n跑SPFA,反之则从n到1跑最短路了。
暂无代码;
第七题:poj1275
题解:每天的工作情况都是一样的,我们只需要求出一天的即可。根据题 意,令s[i]为一天内前i + 1个小时录用的人数。 ◆ 如果i ≥ 7,则s[i] − s[i − 8] ≥ R[i] ◆ 如果0 ≤ i < 7,则可以推出s[23] − s[i + 16] + s[i] ≥ R[i] ◆ 同时每个时刻录用的人数有个上限,假设第i时刻最多可以 录用的人数为b[i],则对于每一 个i有0 ≤ s[i] − s[i − 1] ≤ b[i]。 第二个不等式中包含3个s数组的变量,这该怎么建图呢? 枚举s[23],那么这个量就是已知的了,因此不等式可以变 为s[i] − s[i + 16] ≥ R[i] − s[23]。 既然s[23]是枚举的一天的录用人数的最小数目,我们建图之后求出 的s[23]也应该为枚举的那个值。单调性,二分。
暂无代码;