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;
}
}