【XSY2508】【BZOJ4424】Fairy(二分图)

题面

Description

给定 n n n个点, m m m条边的无向图(无自环),可以从图中删除一条边,问删除哪些边可以使图变成一个二分图。

Input

1 1 1行包含两个整数 n n n, m m m,分别表示点数和边数。

2 ∼ m + 1 2\sim m+1 2m+1行每行两个数 x x x, y y y,表示有一条边连接点 x x x, y y y

Output

第一行两个整数,表示能删除的边的个数。

接下来一行按照从小到大的顺序输出能删除的边的编号。

Sample Input

4 4
1 2
1 3
2 4
3 4

Sample Output

4
1 2 3 4

HINT

10 % 10\% 10%的数据, n , m &lt; = 10 n,m&lt;=10 n,m<=10

40 % 40\% 40%的数据, n , m &lt; = 1000 n,m&lt;=1000 n,m<=1000

70 % 70\% 70%的数据, n , m &lt; = 100000 n,m&lt;=100000 n,m<=100000

100 % 100\% 100%的数据, n , m &lt; = 2000000 n,m&lt;=2000000 n,m<=2000000

题解

考虑如何判断一个图是否为二分图:判断图中有无奇环,这可以用 d e e p deep deep c o l o r color color(染色法)维护。

那么假设图中有 k k k个奇环,要删掉一条边使得新图是二分图,就要把这 k k k个奇环破坏或去掉,也就是把这 k k k个奇环的共边中删掉任意一条。

也就是说,假设原图的某条边在所有的奇环上,就可以被删掉。

但还是有个问题,假设有一条边是两个奇环的共边,那么删掉这条边后两个奇环又会组成一个环,那这个环是奇是偶呢?

答案是偶,因为这个环的总点数等于原来两个奇环的点数之和减 2 2 2,为偶数。

那我们就给每条边设一个 n u m num num,如果有一个奇环经过这条边,就 n u m [ i ] + + num[i]++ num[i]++,最后看一下这条边的 n u m num num是否等于总奇环数就好了。

但这样找边会 W A \color{red}{\mathrm{WA}} WA,为什么?

考虑下图的一种情况:

其中,红色边连着的是一个奇环,蓝色边连着的是一个偶环。

如果按照刚刚的理论,应该这些边都是可以删掉的: ( 1 , 2 ) (1,2) (1,2) ( 1 , 3 ) (1,3) (1,3) ( 2 , 3 ) (2,3) (2,3)

可是我们发现,当我们删掉边 ( 2 , 3 ) (2,3) (2,3)时,新图还是一个奇环( 1 ⟶ 2 ⟶ 4 ⟶ 5 ⟶ 3 ⟶ 1 1\longrightarrow2\longrightarrow4\longrightarrow5\longrightarrow3\longrightarrow1 124531)。

问题就在于,当一条边既在奇环上(设奇环点数为 a a a,则 a a a为奇数),又在偶环上时(设偶环点数为 b b b,则 b b b为偶数),删掉这条边后奇环和偶环会组成一个环,且点数为 a + b − 2 a+b-2 a+b2,是个奇环。

所以当一条边既在奇环上又在偶环上时,我们不能选它。

所以我们还要给每条边设一个 f l a g flag flag判断它在不在偶环上。

记得特判没有奇环的情况,这时任意删掉哪条边都可以。

最后代码如下:

#include<bits/stdc++.h>
 
#define N 4000010
 
using namespace std;
 
int n,m,ans[N];
int cnt,tot,idx,top,head[N],nxt[N],to[N],id[N];
int col[N],num[N],st[N],dfn[N];
bool flag[N],vis[N],use[N];
 
void adde(int u,int v,int ii)
{
    to[++cnt]=v;
    id[cnt]=ii;//id即这条边对应的是输入中的第几条
    nxt[cnt]=head[u];
    head[u]=cnt;
}
 
void dfs(int u,int fa)
{
    dfn[u]=++idx;
    for(int i=head[u];i;i=nxt[i])
    {
        if(dfn[to[i]]>dfn[u]||to[i]==fa)
            continue;
        if(col[to[i]]!=-1)//如果已经访问过
        {
            if(col[to[i]]==col[u])//颜色相等即奇环
            {
                tot++;
                num[id[i]]++;
                for(int j=top;j&&to[st[j]]!=to[i];j--)//循环枚举环上的每一个点
                    num[id[st[j]]]++;//记录num
            }
            else
            {//颜色不等即偶环
                for(int j=top;j&&to[st[j]]!=to[i];j--)
                    flag[id[st[j]]]=true;//标记flag
            }
            continue;
        }
        col[to[i]]=col[u]^1;//染色
        st[++top]=i;//丢进stack
        dfs(to[i],u);
        st[top--]=0;//记得弹出来
    }
}
 
void check(int u)
{
    vis[u]=true;
    for(int i=head[u];i;i=nxt[i])
    {
        if(!use[id[i]]&&((!flag[id[i]]&&num[id[i]]==tot)||!tot))//如果这条边还没有记录在答案里(去重),且没有奇环,或者这条边在所有奇环上且不在偶环上
        {
            use[id[i]]=true;
            ans[++ans[0]]=id[i];
        }
        if(!vis[to[i]])
            check(to[i]);
    }
}
 
int main()
{
    memset(col,-1,sizeof(col));//每个点的颜色(初始化)
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        adde(u,v,i),adde(v,u,i);//建边
    }
    for(int i=1;i<=n;i++)
    {
        if(col[i]==-1)
        {
            tot=0;
            col[i]=0;
            dfs(i,-1);
            check(i);
        }
    }
    sort(ans+1,ans+ans[0]+1);
    printf("%d\n",ans[0]);
    for(int i=1;i<=ans[0];i++)
        printf("%d ",ans[i]);
    return 0;
}
在Android Studio中绘制心电图需要使用自定义View,可以通过继承View或SurfaceView来实现。一般来说,绘制心电图需要以下步骤: 1.获取心电图数据,可以从传感器或文件中读取。 2.将数据转换为坐标点,根据心电图的横纵坐标比例和偏移量计算出每个点的坐标。 3.在自定义View的onDraw()方法中使用Canvas绘制坐标点,可以使用Path或Line等绘制方法。 4.根据需要添加背景、网格线、标尺等辅助元素。 下面是一个简单的示例代码,用于在Android Studio中绘制心电图: ```java public class EcgView extends View { private Paint mPaint; private Path mPath; private float[] mData; // 心电图数据 public EcgView(Context context) { super(context); init(); } public EcgView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(2); mPath = new Path(); } public void setData(float[] data) { mData = data; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mData == null || mData.length == 0) { return; } int width = getWidth(); int height = getHeight(); float xStep = width * 1.0f / mData.length; float yStep = height * 1.0f / 4096; // 假设心电图数据范围为0-4096 mPath.reset(); mPath.moveTo(0, height / 2); for (int i = 0; i < mData.length; i++) { float x = i * xStep; float y = height / 2 - mData[i] * yStep; mPath.lineTo(x, y); } canvas.drawPath(mPath, mPaint); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值