http://acm.hdu.edu.cn/showproblem.php?pid=6736
题意:
仙人掌定义:无重边、自环,每条边被最多一个简单环包含
给定一个图,图中各连通块都是仙人掌
然后让你删除一些边,使得这个图是都是一些树,问你删除的方式有多少种
解析:
删除环上的至少1边,设环长为len,种类有ans1=2^(len)-1种,减掉不删的情况
每个环都要删除ans=ans1*ans2*ans3*....
最后剩下k条边,剩下的边可删可不删,种类有2^k种
转化为求图中各环上边的数目
我们先将入度为1的点及连接的边删除,用一个时间戳来计算,标记点,标记边
ac:
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
#define MAXN 500005
using namespace std;
int to[MAXN<<1],nxt[MAXN<<1],head[MAXN<<1];
int tot=0;
int dfn[MAXN<<1];
int vis[MAXN<<1];
int vis2[MAXN<<1];
ll ans;
ll res;
int in[MAXN<<1];
void init()
{
memset(head,0,sizeof(head));
for(int i=0;i<MAXN;i++)
in[i]=vis[i]=vis2[i]=dfn[i]=0;
ans=1;
res=0;
tot=1;
}
void add(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
ll qpow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
void dfs(int x,int sum)
{
dfn[x]=sum;
vis[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(vis2[i^1]==1)//每条边只走一次
continue;
vis2[i]=1;
if(vis[v]!=0)//该点走过计数
{
res+=dfn[x]-dfn[v]+1;
ans=(ans*(qpow(2,(dfn[x]-dfn[v]+1))-1+mod))%mod;
}
else{
dfs(v,sum+1);
}
}
}
int u[MAXN],v[MAXN];
int main()
{
int t,n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=1;i<=m;i++)
scanf("%d%d",&u[i],&v[i]),in[u[i]]++,in[v[i]]++;
for(int i=1;i<=m;i++)
{
if(in[u[i]]>1&&in[v[i]]>1)
add(u[i],v[i]),add(v[i],u[i]);
}
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
dfs(i,0);
}
ans=(ans*qpow(2,m-res))%mod;
printf("%lld\n",ans);
}
return 0;
}
https://nanti.jisuanke.com/t/39271
题意:
给定n个物品,然后给定m种互斥关系,每个互斥关系有两个物品,这两个物品不能在同一堆
你将这n个物品分成两堆,让这两堆的差尽可能的小,输出大的呢堆的价值
解析:
二分图染色+01背包
首先要差尽可能的小,呢么我们就求sum/2容量下,最多装多少,然后用sum减去,就是答案
这么处理互斥关系呢
我们用二分图染色处理
对于每个连通块,同一种颜色的可以合并,最后得到num个合并后的物品
最后我们求一下容积为sum/2的背包即可,注意这里物品价值都是100的倍数,所以求背包的时候不如将a[i]全部/100,最后在乘回去
降低100倍复杂度
ac:
#include<bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;
int to[MAXN<<1],head[MAXN<<1],nxt[MAXN<<1];
int tot;
int a[MAXN];
int vis[MAXN];
int val[MAXN],num;
int dp[MAXN];
ll aa=0,bb=0;
void init()
{
memset(vis,0,sizeof(vis));
num=0;
memset(head,0,sizeof(head));
memset(dp,0,sizeof(dp));
tot=1;
}
void add(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs(int x,int sign)
{
vis[x]=1;
if(sign)
aa+=a[x];
else
bb+=a[x];
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(vis[v])
continue;
dfs(v,1-sign);
}
}
int main()
{
int t,n,m,u,v,x;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
ll sum=0;
for(int i=1;i<=n;i++)
scanf("%d",&x),a[i]=x/100,sum+=a[i];
for(int i=1;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
for(int i=1;i<=n;i++)//对于每个连通块,同一种颜色的可以合并,最后得到num个合并后的物品
{
aa=0,bb=0;
if(vis[i]==0)
dfs(i,1);
if(aa!=0)
val[++num]=aa;
if(bb!=0)
val[++num]=bb;
}
int m=sum/2;
for(int i=1;i<=num;i++)//进行01背包
for(int j=m;j>=val[i];j--)
dp[j]=max(dp[j],dp[j-val[i]]+val[i]);
int ans=(sum-dp[m])*100;
printf("%d\n",ans);
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=6705
题意:
给定一个图,问图中第k短的路径的长度,单条边可以重复走,不同路径不能完全相同.
解析:
对从同一点出发的边排序,然后装入vector中,方便调用
用广搜处理这题,怎么按顺序得最短是难点
对于一个已知的路径
1.可以选择加一条边得新路径
2.可以现在将上次加的边删去,加上比上次加的边长一些的边,得到新路径(按顺序加边,长度依次递增)
以优先队列处理,第i个弹出的,即为第k小路径
ac:
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define MAXN 100005
using namespace std;
ll ans[MAXN];
int query[MAXN];
int tot=0;
struct edge{
int v,len;
friend bool operator< (edge a,edge b)
{
return a.len<b.len;
}
};
vector<edge> vc[MAXN];
struct node
{
int id,u;
ll len;
friend bool operator <(node a,node b)
{
return a.len>b.len;
}
};
int main()
{
int t,n,m,q,u,v,w;
scanf("%d",&t);
while(t--)
{
for(int i=0;i<MAXN;i++)
vc[i].clear();
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&u,&v,&w),vc[u].pb(edge{v,w});
for(int i=1;i<=n;i++)
sort(vc[i].begin(),vc[i].end());//对同出发点的边排序
int maxs=0;
for(int i=1;i<=q;i++)
{
scanf("%d",&query[i]);
maxs=max(maxs,query[i]);
}
priority_queue<node> que;
for(int i=1;i<=n;i++){
if(vc[i].size())
que.push(node{0,i,vc[i][0].len});
}
int cnt=0;
while(que.size())
{
node x=que.top();
que.pop();
ans[++cnt]=x.len;
if(cnt==maxs)
break;
if(x.id<(vc[x.u].size()-1)){//原出发点还有未加的且更长的边,删除最近加的边,然后加上一个更长的边
que.push(node{x.id+1,x.u,x.len-vc[x.u][x.id].len+vc[x.u][x.id+1].len});
}
int v=vc[x.u][x.id].v;
if(vc[v].size()){//重现选择一条新边加上
que.push(node{0,v,x.len+vc[v][0].len});
}
}
for(int i=1;i<=q;i++)
printf("%lld\n",ans[query[i]]);
}
return 0;
}
补图的最小生成树(set建边)
https://codeforces.com/contest/1243/problem/D
题意:
求你个图的补图的最小生成树
解析:
一开始所以点都没连边,我们把点全部加入一个set中,代表联通块的个数
然后我们bfs,怎么没有实边就是有虚边,我们用set建边,如果set中不存在,就是有边
ac:
#include<bits/stdc++.h>
#define pb push_back
#define MAXN 100005
using namespace std;
set<int> st,vc[MAXN];
int vis[MAXN];
void bfs(int x)
{
queue<int> que,vg;
que.push(x);
st.erase(x);
while(que.size())
{
int x=que.front();
que.pop();
vis[x]=1;
for(auto it=st.begin();it!=st.end();it++)
{
int v=*it;
if(vc[x].find(v)==vc[x].end()){
que.push(v);
vg.push(v);
}
}
while(vg.size()){
int v=vg.front();
st.erase(v);
vg.pop();
}
}
}
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
st.insert(i);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
vc[u].insert(v);
vc[v].insert(u);
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(vis[i]==0){
bfs(i);
ans++;
}
}
printf("%d\n",ans-1);
return 0;
}