CFR-840-Div-2解题报告

比赛传送门

C. Another Array Problem

题意:给你一个数组 a,每次可以选两个位置 i,j(i<j),将 [i,j] 内的所有数替换为 |aiaj|。问最终数组的和最大为多少。

首先一个显然的结论为,任意位置最终结果都不会超过数组最大值。于是可以考虑能不能等于最大值。

注意到将一个区间操作两次则变为 0,再与最大值操作一次即变为最大值。于是对于最大值的两侧都满足大于等于两个元素,则一定可以变为最大值。如果只有一侧满足,则也可以:将满足的那一侧变为最大值,再将不满足的那个元素与最大值操作两次变为 0,最后用满足的那一侧来更新不满足的这一侧。如果两侧都只有不超过一个元素,则分类讨论所有情况即可。

By Um_nik

ll a[5];
int n;

void solve() {
	scanf("%d", &n);
	if (n <= 3) {
		for (int i = 0; i < n; i++)
			scanf("%lld", &a[i]);
		if (n == 2) {
			printf("%lld\n", max(a[0] + a[1], 2 * abs(a[0] - a[1])));
		} else {
			ll ans = max(max(a[0], a[2]), max(abs(a[0] - a[1]), abs(a[1] - a[2])));
			ans *= 3;
			ans = max(ans, a[0] + a[1] + a[2]);
			printf("%lld\n", ans);
		}
	} else {
		ll mx = 0;
		ll x;
		for (int i = 0; i < n; i++) {
			scanf("%lld", &x);
			mx = max(mx, x);
		}
		printf("%lld\n", mx * n);
	}
}

int main()
{
	startTime = clock();
//	freopen("input.txt", "r", stdin);
//	freopen("output.txt", "w", stdout);
	int t;
	scanf("%d", &t);
	while(t--) solve();

	return 0;
}

D. Valid Bitonic Permutations

题意:有一个排列,钦定了其中两位的值 ai=x,aj=y(i<j,xy),问排列为严格山峰形(不能为斜坡)的方案数。

可以考虑区间 DP:设 f[i][j] 表示从大到小填,填完 [i,j] 区间的方案数,则转移考虑下一个元素放左边还是右边即可。

By tourist

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int tt;
  cin >> tt;
  while (tt--) {
    int n, pi, pj, vi, vj;
    cin >> n >> pi >> pj >> vi >> vj;
    --pi; --pj; --vi; --vj;
    auto Valid = [&](int p, int v) {
      if (p == pi && v != vi) return false;
      if (p == pj && v != vj) return false;
      return true;
    };
    vector<vector<Mint>> dp(n, vector<Mint>(n));
    for (int i = 1; i < n - 1; i++) {
      if (Valid(i, n - 1)) {
        dp[i][i] = 1;
      }
    }
    for (int i = n - 1; i >= 0; i--) {
      for (int j = i; j < n; j++) {
        int val = n - 1 - (j - i + 1);
        if (i > 0) {
          if (Valid(i - 1, val)) {
            dp[i - 1][j] += dp[i][j];
          }
        }
        if (j < n - 1) {
          if (Valid(j + 1, val)) {
            dp[i][j + 1] += dp[i][j];
          }
        }
      }
    }
    cout << dp[0][n - 1] << '\n';
  }
  return 0;
}

同样也可以直接组合数推式子:首先令 x<y(否则对称反转一下)。对于 y=n 单独计算一下,主要考虑 x<y<n,i<j 的情况。假设 n 放在位置 m

如果 i<m<j,那么 (y,n) 内的数需要在 m 的左右两边依次放置,每个数有左右两种选择,为 2ny1。对于 (x,y) 内的数,要么在 (i,j) 中靠左放置,要么从 j+1 开始往右放置。而 (i,j) 中剩余空位是确定的,所以要在 (x,y) 中选取固定的数量放到 (i,j) 内,为 (yx1ji(ny))。对于 [1,x) 内的数,要么放在 i 左边,要么放在最右边,而左边只有 i1 个空位,所以为 (x1i1)

如果 j<m,那么 (y,n) 内的数可以放在 (j,m) 内,也可以从 m+1 开始往右放,为 2ny1。对于 (x,y) 内的数,要么放在 (i,j) 内,要么往右继续放,而 (i,j) 内的空位是确定的,所以为 (yx1ji1)。对于 [1,x) 内的数,要么放在 i 左边,要么放在最右边,而左边只有 i1 个空位,所以为 (x1i1)

综上所述,答案为 2ny1(yx1ji(ny))(x1i1)+2ny1(yx1ji1)(x1i1)

需要注意的是,题目规定必须严格山峰,不能为斜坡,所以 mn。如果 m=n 唯一的排列方案为 1,2,,n,只有在 x=i,y=j 时才会出现,减去即可。

By cxm1024

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int MAXV=200;
int inv[MAXV+10],jc[MAXV+10],invjc[MAXV+10];
int ksm(int a,int b,int res=1) {
	for(;b;a=a*a%mod,b>>=1)
		if(b&1) res=res*a%mod;
	return res;
}
void init() {
	jc[0]=1;
	for(int i=1;i<=MAXV;i++)
		jc[i]=jc[i-1]*i%mod;
	invjc[MAXV]=ksm(jc[MAXV],mod-2);
	for(int i=MAXV;i>0;i--)
		invjc[i-1]=invjc[i]*i%mod;
	for(int i=1;i<=MAXV;i++)
		inv[i]=jc[i-1]*invjc[i]%mod;
}
int C(int x,int y) {
	if(y>x||y<0||x<0) return 0;
	return jc[x]*invjc[y]%mod*invjc[x-y]%mod;
}
signed main() {
	init();
	int t;
	cin>>t;
	while(t--) {
		int n,i,j,x,y;
		cin>>n>>i>>j>>x>>y;
		int ii=i,jj=j;
		if(x>y) i=n+1-jj,j=n+1-ii,swap(x,y);
		if(y==n) {
			if(j==n) cout<<0<<endl;
			else cout<<C(n-x-1,j-i-1)*C(x-1,i-1)%mod<<endl;
		}
		else cout<<((ksm(2,n-y-1)*C(y-x-1,j-i-1-(n-y))%mod*C(x-1,i-1)%mod+ksm(2,n-y-1)*C(y-x-1,j-i-1)%mod*C(x-1,i-1)%mod)%mod-(x==i&&y==j)+mod)%mod<<endl;
	}
	return 0;
}

E. Node Pairs

题意:你需要构造一个有向图,使得恰好有 p 个点对能够互相到达。要求用的点尽可能少,在此基础上要使“只能单向可达”的点对尽可能多,分别输出两者的数量。

由于双向可达具有传递性,所以显然答案为若干个 n(n1)2 的和。首先要让 m=n 尽可能小,然后考虑单向可达的点对数量:显然可以让这些连通块之间的点对全部单向可达(用单向完全图的构造方法即可),所以答案为 m(m1)2p

这些答案可以用 DP 维护。设 f[i] 表要恰好有 i 个点对能够互相到达的最少点数,则可以枚举最后一个连通块有几个点转移。复杂度 O(pp)

By tourist

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int p;
  cin >> p;
  const int inf = (int) 1e9;
  vector<int> dp(p + 1, inf);
  dp[0] = 0;
  for (int v = 2; v * (v - 1) / 2 <= p; v++) {
    int u = v * (v - 1) / 2;
    for (int i = 0; i <= p - u; i++) {
      dp[i + u] = min(dp[i + u], dp[i] + v);
    }
  }
  long long ans = dp[p];
  long long ans2 = ans * (ans - 1) / 2 - p;
  cout << ans << " " << ans2 << '\n';
  return 0;
}

F. Edge Queries

题意:给一个无向图,每次询问两个点 a,b,判断 a,b 之间的边中有多少条不是割边。a,b 之间的边定义为存在一个 ab 的简单路径经过这条边。

首先可以边双连通分量缩点,转化成一颗树,问题转化为经过的连通分量的边数之和。但这样显然是有问题的,如下图:

在这种情况下,a,b 之间上面和右面的连通分量都不应该计算贡献。即:一个强连通分量被计算贡献必须经过其中的边。如果只有一个点作为中转不需要计算贡献。

于是可以将缩之前的点之间的边全部删掉,将缩完的点与缩之前的连通分量内的点连起来(菊花图),边权为连通分量的点数。则如果强连通分量之经过了一个点中转,不会计入贡献,而经过边时会计算两次贡献。答案除以二即可。

以下为图示:

对于左边的连通分量,这个图会造成 8 的贡献,而上面和右面的贡献均为 0,所以总的贡献为 4

By tourist

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int n, m;
  cin >> n >> m;
  dfs_undigraph<int> g(n);
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    --x; --y;
    g.add(x, y);
  }
  int cnt;
  auto vc = find_bicone(g, cnt);
  lca_forest<long long> f(n + cnt);
  vector<int> weight(cnt);
  for (auto& e : g.edges) {
    if (vc[e.from] == vc[e.to]) {
      weight[vc[e.from]] += 1;
    } else {
      f.add(e.from, e.to, 0);
    }
  }
  for (int i = 0; i < n; i++) {
    f.add(i, n + vc[i], weight[vc[i]]);
  }
  f.dfs(0);
  f.build_lca();
  int q;
  cin >> q;
  while (q--) {
    int x, y;
    cin >> x >> y;
    --x; --y;
    int w = f.lca(x, y);
    auto ans = f.dist[x] + f.dist[y] - 2 * f.dist[w];
    assert(ans % 2 == 0);
    cout << ans / 2 << '\n';
  }
  return 0;
}

这种把连通分量缩成的点与原图中点连菊花图的方式被称为圆方树,其中缩的点被称为方点,原图中点被称为圆点。一般的圆方树做法是将连通分量的权值存在方点中,询问的时候查询路径的点权和。在这道题中与上面的做法没有本质区别,但有的题中用点权和可以在圆点上记录一些其他信息。

用点权的示例代码 By gyh20

#include<bits/stdc++.h>
#define re register
using namespace std;
const int Mxdt=100000;	//单次大小
inline char gc(){
	static char buf[Mxdt],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
}
#define gc getchar
inline int read(){
	re int t=0;re char v=gc();
	while(v<'0')v=gc();
	while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
	return t;
}
int n,m,stk[400002],tot,head[400002],cnt,dfn[400002],low[400002],tim,val[400002],siz[400002],tp,fa[22][400002],sum[400002],dep[400002];
long long ans;
struct edge{int to,next;}e[800002];
inline void add(re int x,re int y){e[++cnt]=(edge){y,head[x]},head[x]=cnt;}
vector<int>g[400002];
inline void tj(re int x){
	dfn[x]=low[x]=++tim,stk[++tp]=x;
	for(re int i=head[x];i;i=e[i].next)
		if(!dfn[e[i].to]){
			tj(e[i].to);
			low[x]=min(low[x],low[e[i].to]);
			if(low[e[i].to]==dfn[x]){
				val[++tot]=1;
				map<int,int>vis;vis[x]=1;
				while(1){
					++val[tot];
					g[tot].push_back(stk[tp]);
					g[stk[tp]].push_back(tot);
					for(re int j=head[stk[tp]];j;j=e[j].next)if(vis.count(e[j].to))++sum[tot];
					vis[stk[tp]]=1;
					if(stk[tp--]==e[i].to)break;
				}if(sum[tot]==1)sum[tot]=0;
				g[x].push_back(tot);
				g[tot].push_back(x);
			}
		}
		else low[x]=min(low[x],dfn[e[i].to]);
}
inline void dfs(re int x,re int y){
	fa[0][x]=y,dep[x]=dep[y]+1;
	for(re int i=1;i<=20;++i)fa[i][x]=fa[i-1][fa[i-1][x]];
	for(re int i=0;i<g[x].size();++i){
		re int z=g[x][i];
		if(z^y)sum[z]+=sum[x],dfs(z,x);
	}
}
int main(){
	tot=n=read(),m=read();
	for(re int i=1,x,y;i<=m;++i)x=read(),y=read(),add(x,y),add(y,x);
	tj(1),dfs(1,1);
	int q=read();
	while(q--){
		re int x=read(),y=read(),oo=sum[x]+sum[y];
		if(dep[x]<dep[y])swap(x,y);
		for(re int i=20;~i;--i)if(dep[fa[i][x]]>=dep[y])x=fa[i][x];
		for(re int i=20;~i;--i)if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
		if(x!=y)x=fa[0][x];
		oo-=sum[x],oo-=sum[fa[0][x]];
		printf("%d\n",oo);
	}
}

参考博客

posted @   曹轩鸣  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起