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;
}
D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5638
官方题解:
参考下普通的用堆维护求字典序最小拓扑序, 用某种数据结构维护入度小于等于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;
}