暑假集训日记——7.9/7.10(scc习题+codeforce)

F. Simple Cycles Edges
题意:问你给的m条边里面有几条是只属于一个简单环的。
题解:
要求求简单环,所以可以用tarjan求出点双连通分量,然后判断这个点双连通分量中的点的个数是否等于边的条数(有且仅有边的条数等于点的个数,才是个简单环),如果是的话把连通分量中的所有边存到一个数组中,最后找出所有边之后对边的编号进行排序输出就好了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=1e6+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll mul(ll a,ll b){return (a%mod*b%mod)%mod;}
//ll pre(ll a,ll b){return (a%mod/b%mod)%mod;}
ll n,m,k,x,y,z;

const int maxn=1e5+10;
int bccno[maxn];///点所在的双联通分量编号--割点无意义
int iscut[maxn];///点是否是割点
int low[maxn];///low[u]表示点u及其后代能连回到的最早的祖先
int pre[maxn];///pre[u]表示点u第一次被访问到时的时间戳
int dfs_clk=0,bcc_cnt=0;

struct note
{
    int x,id;
};

vector<note>G[maxn];
vector<int>bccp[maxn],vis[maxn];
struct Edge{int u,v,id;Edge(int _u=0,int _v=0,int _id=0){u=_u;v=_v;id=_id;}};
stack<Edge>S;

int a[N],step[N];

void dfs(int u,int fa){
    low[u]=pre[u]=++dfs_clk;
    int sz=G[u].size();
    int child=0;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i].x;
        int w=G[u][i].id;
        if(!pre[v]){
            S.push(Edge(u,v,w));
            child++;
            dfs(v,u);
            low[u]=min(low[u],low[v]);      ///由其子孙结点更新
            if(low[v]>=pre[u]){             ///此时u为割点
                ++bcc_cnt;
                bccp[bcc_cnt].clear();
                vis[bcc_cnt].clear();
                for(;;){
                    Edge e=S.top();S.pop();
                    if(bccno[e.u]!=bcc_cnt)
                        bccno[e.u]=bcc_cnt,bccp[bcc_cnt].push_back(e.u);
                    if(bccno[e.v]!=bcc_cnt)
                        bccno[e.v]=bcc_cnt,bccp[bcc_cnt].push_back(e.v);
                    vis[bcc_cnt].push_back(e.id);
                    if(e.u==u&&e.v==v)break;///遇到边(u,v)时退出
                }
            }
        }
        else if(pre[v]<pre[u]&&v!=fa)        //用反向边更新low[u]
            S.push(Edge(u,v,w)),low[u]=min(low[u],pre[v]);
        /*实际上也可以直接写为:
        else if(pre[v]<low[u]&&v!=f)
            S.push(Edge(u,v)),low[u]=pre[v];
        */
    }
    if(fa<0&&child==1) iscut[u]==0;
}
void Tarjan(int n)
{
    memset(pre, 0, sizeof(pre));
    memset(iscut, 0, sizeof(iscut));
    memset(bccno, 0, sizeof(bccno));
    dfs_clk = bcc_cnt = 0;
    for (int i = 1; i <=n; i++)
        if (!pre[i]) dfs(i,-1);
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        G[x].push_back(note{y,i});
        G[y].push_back(note{x,i});
    }
    Tarjan(n);
    int cnt=0;
    for(int i=1;i<=bcc_cnt;i++)
    {
        //cout<<vis[i].size()<<" "<<bccp[i].size()<<endl;
        if(vis[i].size()==bccp[i].size())
        {
            for(int j=0;j<vis[i].size();j++)
                if(step[vis[i][j]]==0)
                {
                    a[cnt++]=vis[i][j];
                    step[vis[i][j]]=1;
                }
        }
    }
    if(cnt==0)
    {
        cout<<0<<endl;
        return 0;
    }
    sort(a,a+cnt);
    cout<<cnt<<endl;
    for(int i=0;i<cnt;i++)
        cout<<a[i]<<" ";

}

E - Road Construction
啥都不说了,长点心吧…
题解:
边双联通+缩点+结论
若要使得任意一棵树,在增加若干条边后,变成一个双连通图,那么
至少增加的边数 =( 这棵树总度数为1的结点数 + 1 )/ 2

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=1e4;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll mul(ll a,ll b){return (a%mod*b%mod)%mod;}
//ll pre(ll a,ll b){return (a%mod/b%mod)%mod;}
ll n,m,k,z;

const int maxn = 1005;
int dfn[maxn],low[maxn],tot;
vector<int> g[maxn];
bool isB[maxn][maxn];

//找到桥
void tarjan(int u,int fa) {
    dfn[u] = low[u] = ++tot;
    for(int i = 0; i < (int)g[u].size(); i++) {
        int v = g[u][i];
        if(!dfn[v]) {
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
            if(low[v]>dfn[u])//判断
                isB[u][v] = isB[v][u] = true;
        }
        else if(fa!=v) {
            low[u] = min(low[u],dfn[v]);
        }
    }
}

//边双连通标号
int bcc_cnt;
int bccno[maxn];
void dfs(int idx) {
    dfn[idx] = 1;
    bccno[idx] = bcc_cnt;
    for(int i = 0; i < (int)g[idx].size(); i++) {
        int v = g[idx][i];
        if(isB[idx][v])
            continue;
        if(!dfn[v])
            dfs(v);
    }
}


void find_ebcc(int n) {
    bcc_cnt = tot = 0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(bccno,0,sizeof(bccno));
    memset(isB,0,sizeof(isB));

    for(int i = 1; i <= n; i++) {
        if(!dfn[i]) {
            tarjan(i,-1);
        }
    }
    memset(dfn,0,sizeof(dfn));
    for(int i = 1; i <= n; i++) {
        if(!dfn[i]) {
            bcc_cnt++;
            dfs(i);
        }
    }
}
int x[N],y[N],cnt[N];

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    scanf("%d%d",&n,&m);
    for(int i = 1; i <=m; i++) {
        scanf("%d%d",&x[i],&y[i]);
        g[x[i]].push_back(y[i]);
        g[y[i]].push_back(x[i]);
    }
    find_ebcc(n);
    for(int i=1;i<=m;i++)
    {
        if(bccno[x[i]]!=bccno[y[i]])
            cnt[bccno[x[i]]]++,cnt[bccno[y[i]]]++;//缩后的点!!!!
    }
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        //cout<<cnt[i]<<endl;
        if(cnt[i]==1)
        {
            sum++;
        }
    }
    cout<<(sum+1)/2<<endl;
}

C. Flag
题解:模拟题
先扫的列,查找有多少个符合题意并且宽度为1的国旗,然后横向扫,判断这些宽度为一的国旗是否可以和之前的国旗拼成一个大国旗,不能的话答案加1,能得话答案+w+1。
(w表示之前扫过的,并且可以与这个国旗拼在一起的,国旗的宽度)

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=5e5+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll mul(ll a,ll b){return (a%mod*b%mod)%mod;}
//ll pre(ll a,ll b){return (a%mod/b%mod)%mod;}
ll n,m,k,x,y,z;
string s[1001];
int a[1005][1005];
int dp[1005][1005];

int slove()
{
    vector<int>e;
    int ans=0;
    for(int j=1;j<=m;j++)
    {
        int t=1;
        e.clear();
        for(int i=1;i<=n-1;i++)
        {
            if(a[i][j]==a[i+1][j])
                t++;
            else
                e.push_back(t),t=1;
        }
        e.push_back(t);
        int num=0;
        for(int i=0;i+2<e.size();i++)
        {
            int flag=0;
            if(e[i]>=e[i+1]&&e[i+1]<=e[i+2])
            {
                int r=num+e[i]+2*e[i+1];///计算国旗上下宽度
                int l=r-e[i+1]*3+1;
                for(int k=l;k<=r;k++)
                {
                    if(a[k][j]!=a[k][j-1])///不能拼成一面大国旗
                    {flag=1;break;}
                }
                if(flag==1) dp[r][j]=1;
                else dp[r][j]=dp[r][j-1]+1;

                ans+=dp[r][j];
            }
            num+=e[i];///移到下一条纹的位置
        }
    }
    return ans;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
   cin>>n>>m;
   for(int i=0;i<n;i++)
    cin>>s[i];
    int ans;
   for(int i=1;i<=n;i++)
   {
       for(int j=1;j<=m;j++)
       {
           a[i][j]=s[i-1][j-1]-'a'+1;
       }
   }
   ans=slove();
   cout<<ans<<endl;

}

D. Irrigation
题解:
让我们同时解决所有的查询。为此目的,按递增顺序对它们进行排序。根据前n年举办的比赛数量对所有国家进行排序(见图)
在这里插入图片描述
经过几年的竞争,这张图表发生了什么变化?单元格从较低的行填充到较高的行,而在一行中,我们根据国家编号对单元格进行排序。
让我们从下往上填这张表。
对于不会在当前行中回答的查询,当前行中单元格的颜色顺序并不重要,重要的是数量。因此,对于这样的查询,我们可以简单地累积到目前为止已经绘制的单元格的数量。
现在,让我们讨论需要在当前行中回答的查询。如果我们从k(查询参数)中减去前一行中绘制的单元格数S,那么我们只需要返回这个集合中的k - S th元素。换句话说,我们需要在集合中添加国家,有时还需要计算其中的i-th元素。我们可以用笛卡尔树“treap”或者段树来做。
也有可能,在我们填满所有的图表之后,仍然有一些问题没有得到解答。在本例中,我们可以注意到,所有后续行看起来都像是整个国家集。
所以答案就是k - S模m的余数。
由于在图表填满之前,我们最多只需要考虑n行,所以解决方案在O(q+n+m)log中工作。

看了下解决的代码:
可以用线段树或者树状数组,大概明白题意,具体怎么操作还是没搞懂,先挂着找时间补一下
而且看到一个奇奇怪怪的做法,建立一个矩形单元格进行二叉树查询

代码先欠着

A - COURSES
题解:
一开始超时,还以为是不是邻接表太慢,结果是输入输出的问题,我以为100*300的输出也不算大呢…
剩下的就是一道求最大匹配的裸题了

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=305;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll mul(ll a,ll b){return (a%mod*b%mod)%mod;}
ll pre(ll a,ll b){return (a%mod/b%mod)%mod;}
ll n,m,k,x,y,z;

vector<int>E[105];
int vis[N];///标记是否访问
int cy[N];///储存右端点匹配的对象
int cx[N];///储存左端点匹配的对象

bool finds(int x)
{
    for(int i=0;i<E[x].size();i++)
    {
        if(!vis[E[x][i]])
        {
            vis[E[x][i]]=1;
            if(!cy[E[x][i]]||finds(cy[E[x][i]]))
            {
                cx[x]=E[x][i];
                cy[E[x][i]]=x;
                return 1;
            }

        }
    }
    return 0;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
   int t;
   scanf("%d",&t);
   while(t--)
   {
       memset(cy,0,sizeof(cy));
       memset(cx,0,sizeof(cx));
       scanf("%lld%lld",&n,&m);
       for(int i=0;i<n;i++)///建图
       {
           scanf("%lld",&k);
           E[i+1].clear();
           for(int j=0;j<k;j++)
           {
               scanf("%lld",&x);
               E[i+1].push_back(x);
           }

       }
       int ans=0;///记录匹配数
       for(int i=1;i<=n;i++)///依次访问左端点
       {
           memset(vis,0,sizeof(vis));///将所有的右端点统计为未访问
           if(finds(i)) ans++;///找到一条增广路匹配数加一
       }
        if(ans==n)
             printf("YES\n");
        else
            printf("NO\n");
   }

}

B - Asteroids

题意:给出一个N*N的矩阵,有的位置有行星。已知一枪可以击溃一行或一列上的所有行星。问最少多少枪可以击溃所有的行星。

题解:以行作为X集合,以列作为Y集合,一个行星在(x,y),则x对应X中的点向y对应Y中的点连一条边,则某个顶点一旦被选,则与之相连的边(也就是行星)都会被选,也就是选出最少的顶点覆盖所有的边,即最小顶点覆盖。

Konig定理:一个二分图中的最大匹配数等于这个图中的最小点覆盖数。
最小点覆盖数:假如选中了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点覆盖所有的边。

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=505;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
ll mul(ll a,ll b){return (a%mod*b%mod)%mod;}
ll pre(ll a,ll b){return (a%mod/b%mod)%mod;}
ll n,m,k,x,y,z;

vector<int>E[505];
int vis[N];///标记是否访问
int cy[N];///储存右端点匹配的对象
int cx[N];///储存左端点匹配的对象

bool finds(int x)
{
    for(int i=0;i<E[x].size();i++)
    {
        if(!vis[E[x][i]])
        {
            vis[E[x][i]]=1;
            if(!cy[E[x][i]]||finds(cy[E[x][i]]))
            {
                cx[x]=E[x][i];
                cy[E[x][i]]=x;
                return 1;
            }

        }
    }
    return 0;
}

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

       cin>>n>>m;
       for(int i=0;i<m;i++)///建图
       {
            cin>>x>>y;
            E[x].push_back(y);
       }
       int ans=0;///记录匹配数
       for(int i=1;i<=n;i++)///依次访问左端点
       {
           memset(vis,0,sizeof(vis));///将所有的右端点统计为未访问
           if(finds(i)) ans++;///找到一条增广路匹配数加一
       }
       cout<<ans<<endl;
}

C - Soldier and Traveling
题意:
给定n个顶点m条边的无向图,每个点上有若干名士兵,每名士兵只能留在原地或移动到相邻点上,给出移动后n个顶点的士兵人数,问是否存在一种移动方案.

题解:
建图,对i=1,2,…,n,源点S向标号i的点连容量为a[i]的边,标号i+n的点向汇点T连容量为b[i]的边,标号i的点向标号i+n的点连容量为INF的边(表示留在原地),若原图中i,j两点间有边相连,则标号i的点向标号j+n的点连容量为INF的边(表示可以从i到j),标号j的点向标号i+n的点连容量为INF的边(表示可以从j到i),跑一次从S到T的最大流,如果最大流既与所有a[i]之和相等,又与所有b[i]之和相等,则有解。

网络流的主要难点就在于怎么构建合适的网络图!!!

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int N=305;
const int inf=0x3f3f3f3f;
struct Edge
{
    int from,to,cap,flow;//顶点-顶点,容量,流量
    Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f) {}
};
vector <Edge> edges;
vector <int > G[N];
int d[N],cur[N],num[N][N];
int n,m,S,T,a,b,C;
bool vis[N],flag;
void init()
{
    for(int i=0; i<=n; i++)
    {
        G[i].clear(),edges.clear();
    }
}
void add_edge(int from,int to,int cap)
{
    edges.push_back(Edge(from,to,cap,0));
    edges.push_back(Edge(to,from,0,0));
    C=edges.size();
    G[from].push_back(C-2);
    G[to].push_back(C-1);
}
bool bfs(int s,int t)
{
    memset(vis,false,sizeof(vis));
    queue <int >Q;
    d[s]=0;
    Q.push(s);
    vis[s]=true;
    while(!Q.empty())
    {
        int q=Q.front();
        Q.pop();
        for(int i=0; i<G[q].size(); i++)
        {
            Edge &e=edges[G[q][i]];
            if(!vis[e.to]&&e.cap>e.flow)
            {
                d[e.to]=d[q]+1;
                Q.push(e.to);
                vis[e.to]=true;
            }
        }
    }
    return vis[T];
}
int dfs(int s,int k)
{
    if(s==T||k==0) return k;
    int f,flow=0;
    for(int &i=cur[s]; i<G[s].size(); i++)
    {
        Edge &e=edges[G[s][i]];
        if(d[e.to]==d[s]+1&&(f=dfs(e.to,min(k,e.cap-e.flow)))>0)
        {
            e.flow+=f;
            edges[G[s][i]^1].flow-=f;
            flow+=f;
            k-=f;
            if(k==0) break;
        }
    }
    return flow;
}
int max_flow(int s,int t)
{
    int flow=0;
    while(bfs(s,t))
    {
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,inf);
    }
    return flow;
}


int main() {
    int x,y,z;
    init();
    scanf("%d%d",&n,&m);
    S=0,T=2*n+1;
    int sum=0,cnts=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        sum+=x;
        add_edge(0, i, x);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        cnts+=x;
        add_edge(i+n, 2*n+1, x);
    }
    int u, v, w;
    for(int i = 0; i < m; i++){
        scanf("%d%d", &u, &v);
        add_edge(u, v+n, inf);
        add_edge(v, u+n, inf);
    }
    for(int i = 1;i <= n ;i++)
    {
        add_edge(i, i+n, inf);
    }
    if(sum!=cnts)
    {
        puts("NO");
        return 0;
    }
    int ans=max_flow(S,T);
    //cout<<ans<<endl;
    if(sum!=ans)
    {
        puts("NO");
    }
    else
    {
        puts("YES");
        for(int i=1; i<=n; i++)
        {
            for(int j=0; j<G[i].size(); j++)
            {
                Edge&e=edges[G[i][j]];
                if(e.to>0) num[i][e.to-n]=e.flow;
            }
        }
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
                printf("%d ",num[i][j]);
            puts("");
        }
    }

}

B. The Golden Age
题解:
没啥题解,暴力,暴力出奇迹

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<functional>
#include<set>
#include<map>
#include<iterator>
#include<queue>
#include<vector>
#include<string>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
#define mp make_pair

const int N=1e7+10;
const long long INF=1e18;
const double eps=0.0000001;
const ll mod=1e9+7;
const ll inf=1e18+100000;
ll totl[N];

int main()
{
   ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
   ll x,y,l,r;
   cin>>x>>y>>l>>r;
   vector<ll>cntx,cnty;
   //cntx.push_back(1),cnty.push_back(1);
   ll xy=1,yx=1;
   while(r/xy)
   {
       cntx.push_back(xy);
       if(xy>inf/x) break;//数据很大注意不要让次幂超long long
       xy*=x;
   }
    while(r/yx)
   {
       cnty.push_back(yx);
       if(yx>inf/y) break;
       yx*=y;
   }
   ll sum=0;
    for(int i=0;i<cntx.size();i++)
        for(int j=0;j<cnty.size();j++)
        {
           if(cntx[i]+cnty[j]>=l&&cntx[i]+cnty[j]<=r)
            {
                totl[sum++]=cntx[i]+cnty[j];
            }
        }
    if(sum!=0)//特判全为黄金时段
    {
       sort(totl,totl+sum);
       ll ans=0;
       ans=max(ans,totl[0]-l);
       for(int i=1;i<sum;i++)
       {
            ans=max(ans,totl[i]-totl[i-1]-1);
       }
       ans=max(ans,r-totl[sum-1]);
       cout<<ans<<endl;
    }
   else
   {
       cout<<r-l+1<<endl;
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值