A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5635
官方题解:
如果ai=x, 那么可以推断出si=si+1=...=si+x, 并且如果ai≠0, 那么ai+1=ai−1, 利用第二个条件判断无解, 利用第一个条件划分等价类. 假设有m个等价类, 那么答案就是26⋅25m−1
我的思考:
简单模拟题,还是容易考虑不全面,首先最长公共前缀的长度不能大于后面的最大长度,然后a[i]!=0时a[i+1]必然等于a[i]
#include<cstdio> #include<cstring> using namespace std; #define LL __int64 int T,i,n,a[100005]; LL ans; const int mod=1e9+7; int main() { scanf("%d",&T); a[1]=0; while(T--) { scanf("%d",&n); ans=26; for(i=2;i<=n;i++) scanf("%d",&a[i]); for(i=2;i<=n;i++) { if(n-i+1<a[i]||(a[i-1]!=0&&a[i]!=a[i-1]-1)) { ans=0; break; } if(a[i]==0) ans=(ans*25)%mod; } printf("%I64d\n",ans); } return 0; }
B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5636
官方题解
你可以选择分类讨论, 但是估计可能会写漏一些地方. 只要抽出新增边的端点作为关键点, 建立一个新图, 然后跑一遍floyd就好了. 复杂度大概O(62⋅m)
我的思考:
由于只是新加三个点,所以对于每一个询问,可以暴力枚举所有可能的捷径组合,之前在比赛的时候只考虑到经过一次捷径,没有考虑到可以多条捷径组合 too young to naive
#include<cstdio> #include<cstring> #include<cmath> #define LL __int64 using namespace std; const int MOD=1e9+7; int t,f[6],a[6]; LL ans,cnt; LL min(LL x,LL y) { return x>y?y:x; } void dfs(int s,int dist) { ans=min(ans,abs(s-t)+dist); for(int i=0;i<6;i++) { if(f[i]) { f[i]=f[i^1]=0; if(dist+abs(s-a[i])+1<ans) dfs(a[i^1],dist+abs(s-a[i])+1); f[i]=f[i^1]=1; } } } int main() { int T,i,n,m,s; int cnt; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(i=0;i<6;i++) scanf("%d",&a[i]),f[i]=1; cnt=0; for(i=1;i<=m;i++) { scanf("%d%d",&s,&t); ans=MOD; dfs(s,0); cnt=(cnt+i*ans)%MOD; } printf("%d\n",cnt); } return 0; }
官方题解:
注意到答案实际上只和s⊕t有关, bfs预处理下从0到x的最短步数, 然后查询O(1)回答即可.
我的思考:
正如官方题解所说 s^a^b^c……=t 等价于 a^b^c……=s^t
所以原题目就转换乘按题目所给条件从0转化成s^t最短需要几步,预处理情况,之后的询问只要查询数组就可以了
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int mod=1e9+7; const int maxn=1e5+4e4; int T,n,m,ans; int a[20],f[maxn],x,y; int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) scanf("%d",&a[i]); memset(f,1,sizeof(f)); queue<int>q; q.push(0); f[0]=0; while(!q.empty()) { int u=q.front();q.pop(); for(int i=0;i<n;i++) { if(f[u^a[i]]>f[u]+1) { f[u^a[i]]=f[u]+1; q.push(u^a[i]); } } for(int i=0;i<17;i++) { if(f[u^(1<<i)]>f[u]+1) { f[u^(1<<i)]=f[u]+1; q.push(u^(1<<i)); } } } int res=0; for(int k=1;k<=m;k++) { scanf("%d%d",&x,&y); (res+=k*f[x^y])%=mod; } printf("%d\n",res); } return 0; }
官方题解:
参考下普通的用堆维护求字典序最小拓扑序, 用某种数据结构维护入度小于等于k的所有点, 每次找出编号最小的, 并相应的减少k即可.
这个数据结构可以用线段树, 建立一个线段树每个节点[l,r]维护编号从l到r的所有节点的最小入度, 查询的时候只需要在线段树上二分, 找到最小的x满足入度小于等于k.
复杂度O((n+m)logn)
我的思考:
可以用优先队列来做,对于拓扑序列,只要入度为0,那么就可以作为当前的根节点加入序列当中,所以我们可以先求出所有点的入度,按编号升序加入优先队列,我们知道要想使得字典序最小,我们要不惜一切代价使得当前的每一个位置,能取到尽可能小的点,而完全不用顾忌我们要消耗多少的机会来达到这一目的
#include<cstdio> #include<queue> #include<cstring> #include<vector> using namespace std; typedef long long ll; const ll mod=1e9+7; const int maxn=1e5+5; const int maxm=2e5+5; int in[maxn]; vector<int>a[maxn]; bool vis[maxn]; typedef pair<int,int> p; int main() { int T; scanf("%d",&T); while(T--) { int n,m,k; scanf("%d%d%d",&n,&m,&k); memset(in,0,sizeof(in)); for(int i=0;i<=n;i++)a[i].clear(); while(m--) { int x,y; scanf("%d%d",&x,&y); a[x].push_back(y); in[y]++; } priority_queue<int,vector<int>,greater<int> >q; for(int i=1;i<=n;i++){ q.push(i); } ll ans=0,cnt=1; memset(vis,false,sizeof(vis)); while(!q.empty()) { int temp=q.top(); q.pop(); if(k<in[temp])continue; if(vis[temp])continue; k-=in[temp]; vis[temp]=true; ans+=((temp*cnt)%mod);ans%=mod; cnt++; int tx=temp; int num=a[tx].size(); for(int i=0;i<num;i++){ in[a[tx][i]]--; if(!vis[a[tx][i]]) q.push(a[tx][i]); } } printf("%I64d\n",ans); } return 0; }E题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5639
官方题解:
考虑删掉的边的形态, 就是我们经常见到的环套树这种结构, 参考平时这种图给出的方法, 如果一个图的每个点的出边只有一条, 那么一定会构成环套树这种结构. 于是问题可以转化成, 给无向图的每条边定向, 使得出度最大点的出度最小 (每个点的出度大小对应了删的次数).
显然, 这个东西使可以二分的, 不妨设二分值为x. 考虑混合图的欧拉回路的做法, 利用网络流判合法. 先给每条无向边随便定向, 对于出度大于x的, 从源点连一条流量为deg−x的边, 对于出度小于x的, 从这个点连一条流量为x−deg的边到汇点. 对于原来图中的边, 流量为1加到网络流图中. 只要满流就是合法.
类似方法一, 要求的无非是每条边的归属问题, 对于每条边(a,b), 它可以属于a或者b, 那么新建一个节点表示这条边并和a,b都相邻, 这样就得到了一个二分图. 左边是原图中的节点, 右边是原图中的边. 二分每个左边每个节点的容量k, 如果右边的点能够完全匹配, 那么这个k就是可行的, 找到最小的k即可. 转化成二分图多重匹配问题.
事实上这题存在O(nm)的做法, 只要在方法二的基础上继续改进就好了, 二分是没有必要的. 注意到每次增广的时候, 增光路中只有端点的容量会变化, 增广路中间的点的容量都不会变化. 那么之要每次增广到端点容量最小的那个点就好了.
我的思考:设经过k次 我们完成了所有的删边操作,所以我们二分k
把所有无向边转换成有向边
我采取的是方法2 新增除了原先的n个节点之外 把每条边看成是新的节点 也就是说新增m个节点,然后跑一下最大流
建图
1.s向所有m个边节点 连一条容量为1的边
2.每个边结点 向他的顶点的节点 连一条容量为1的边
3.每个顶点节点向t连一条容量为k的边,其中k是我们当前二分的答案,如果最大流==m 那么当前是可行方案
然后考虑为什么这样建边能满足题目所给的条件呢,题目要求删边,但每次删的边中不能存在两个环,这个在最大流计算中是这样体现的,我们进行k次删边,然后如果删的边中存在一个环 那么这样进行的一次删边会占据所有删的边的节点到t的边的流量1,然后如果删一个存在两个环的图的话,因为我们知道两个环的图 如果其中有n条边,那么他的节点只有n-1个,也就是说会有一个顶点节点到t的边容量会是2,2的意义也就是说要进行2次删边,所以原问题就完全转换成了上述网络流过程
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int N=2e3+10; int T,n,m,x[N],y[N]; struct Maxflow { const static int maxe=2e6+10; const static int maxp=1e5+10; const static int INF=0x7ffffff; struct node { int x,f; node(){} node(int x,int f):x(x),f(f){}; }edge[maxe]; int head[maxp],next[maxe],dist[maxp],tot,work[maxp],n; void clear(int x){n=x;tot=0;for(int i=0;i<=n;i++) head[i]=-1;} void addedge(int s,int t,int f) { edge[tot]=node(t,0);next[tot]=head[s];head[s]=tot++; edge[tot]=node(s,f);next[tot]=head[t];head[t]=tot++; } bool bfs(int s,int t) { for (int i=0;i<=n;i++) dist[i] = -1; queue<int>p; p.push(s); dist[s]=0; while(!p.empty()) { int q=p.front();p.pop(); for(int i=head[q];i!=-1;i=next[i]) { if (edge[i^1].f&&dist[edge[i].x] ==-1) { p.push(edge[i].x); dist[edge[i].x]=dist[q]+1; if(dist[t]!=-1) return true; } } } return false; } int dfs(int s,int t,int low) { if(s==t)return low; for (int &i=work[s],x;i>=0;i=next[i]) { if(dist[s]+1==dist[edge[i].x]&&edge[i^1].f&&(x=dfs(edge[i].x,t,min(low,edge[i^1].f)))) { edge[i].f+= x; edge[i^1].f-=x; return x; } } return 0; } int dinic(int s,int t) { int maxflow=0,inc=0; while (bfs(s,t)) { for(int i=0;i<=n;i++) work[i]=head[i]; while(inc=dfs(s,t,INF)) maxflow+=inc; } return maxflow; } }solve; bool check(int flow) { solve.clear(n+m+1); for(int i=1;i<=m;i++) { solve.addedge(i,0,1); solve.addedge(m+x[i],i,1); solve.addedge(m+y[i],i,1); } for(int i=m+1;i<=m+n;i++) solve.addedge(n+m+1,i,flow); return solve.dinic(n+m+1,0)==m; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]); if(m==0) {printf("0\n");continue;} int left=1,right=m; while(left<=right) { int mid=(left+right)/2; if(check(mid)) right=mid-1;else left=mid+1; } printf("%d\n",left); } return 0; }