2020 牛客多校第一场

2020 牛客多校第一场

A. B-Suffix Array

后缀数组的思想:倍增+桶排序的方式找出一串连续序列后缀的大小。虽说正常使用的时候都是字典序,但是只要修改排序方式,也能够达到一个类似的"后缀数组“效果。

当然直接修改排序方式可能比较麻烦(都是抄板子),那么不如就对应排序方式修改下原数组的值做个变换就行。

题目解法来自论文,。。猜的对就能做

B. Infinite Tree

虚树的应用:
显然点太多不可能全部找出来,只有lca能来点作用

问题就是怎么找lca,
使用一个树状数组维护各个因数个数

由于树的转移都是找的最小因数,显然lca即为降序排序后,最大相同长度因数的乘积,然后这个乘积其实可以不需要,只求深度即可(显然一直增大)

可以通过反证法证明 v点比v点号的条件是: f [ u ] < 2 f [ v ] f[u] < 2 f[v] f[u]<2f[v]

C. Domino 不会

D. Quadratic Form

。。。。拉格朗日乘子法, 推公式

E.Counting Spanning Trees 论文题

F.Infinite String Comparision

普通做法:找循环节比较大小(KMP)
神仙做法:看成26进制下的无限循环小数
∣ S ∣ ( 26 ) 2 6 ∣ s ∣ − 1 \frac{|S|_{(26)}}{26^{|s|}-1} 26s1S(26)
之后通过式子变换可以直接比较S1+S2和S2+S1

补一个循环节:记得判长度与n-next[n]长度是否除尽

string getCycleString(const string & str){

	int n = str.size();
	vector<int> nxt(n + 1, 0);
	for (int i = 1 ; i < n ; ++i){
		int j = i;
		while(j > 0){
			j = nxt[j];
			if(str[i] == str[j]){
				nxt[i+1] = j + 1;
				break;
			}
		}
	}
	int cycleLen = n - nxt[n];
	if(n % cycleLen == 0)
		return str.substr(0,cycleLen);
	else
		return str;
}

H. Minimum-cost Flow

给定每天边的价格
每次询问 u i v i \frac{u_i}{v_i} viui,表示每条边的容量,求流量为1最小费用流

网络流的理解:对于能在残量网络上继续跑的算法,EK,Dinic, 通常都会有一个
while(bfs()) maxflow += run();表示选择一条路径进行增广

而在费用流里,每次增广的路径都是开销最小的。
换句话说,假设 F i F_i Fi为第i次跑的流量, C i C_i Ci为费用

显然可以得出:
当最大流限制在 [ F i − 1 + 1 , F i ] [F_{i-1}+1,F_{i}] [Fi1+1,Fi]的时候,我们选择的这条增广路径是不会变的,假设这条增广路径开销为 V V V,那么对于 F i − 1 < F ≤ F i F_{i-1} < F \leq F_i Fi1<FFi的流量 F F F,
显然有 C o s t = C i − 1 + ( F − F i − 1 ) ∗ V Cost = C_{i-1} + (F-F_{i-1})*V Cost=Ci1+(FFi1)V , V V V显然可以通过相邻两个C推出。

I. 1 or 2

给出一些边和点,挑出子图使得恰好有 d i ∈ { 1 , 2 } d_i \in \{1,2\} di{1,2}条边与点 V i V_i Vi相连。
肯定是匹配问题,匹配问题必须要认清哪些是点哪些是边,这题就如题意

显然很难分成两个独立集合,看到数据50,100,并且d有限,考虑一般图匹配

主要是建模部分,对于连接 ( u , v ) (u,v) (u,v)的一条边,有3种情况

d u = d v = 1 d_u = d_v = 1 du=dv=1 显然直接连即可
d u = 1 , d v = 2 d_u = 1 , d_v = 2 du=1,dv=2 拆点直接连即可
d u = d v = 2 d_u = d_v = 2 du=dv=2 不可直接两两互联,因为边只能用一次 ,所以对于这条边,开两个点 E i , E i ′ E_i,E_{i'} Ei,Ei使他们相连,并且 E i E_i Ei连接u的两点 E i ′ E_{i'} Ei连接v的两点。

跑一般图最大匹配后再判断是否所有都满足条件。

在写的时候我也尝试过把第三种写法应用在第二种上,也就是u-e1-e2-{v,v’}的构图方式,但后来发现有问题。

原因是:可能会出现u-e1被选择,v,v’早已被选择,而e2被孤立的情况。 我们求的虽然是最大匹配,但是由于边选择了,两个点必须也要被选。点选了,必须要有相应的边,不可能出现孤立的情况,所以我们要在保证是最大匹配的同时保证完美匹配

使用第二种构建方式的代码:

#include <bits/stdc++.h>
 
const int N = 1e3 + 100;
 
int n, m, match[N], pre[N];
int col[N], fa[N];
int que[N], head, tail;
std::vector<int> G[N];
 
int find(int x) {
  return x == fa[x] ? x : fa[x] = find(fa[x]);
}
 
int hash[N], hash_cnt;
// 暴力找LCA,交替爬升
inline int get_lca(int x, int y) {
  ++hash_cnt;
  while (hash[x] != hash_cnt) {
    if (x) hash[x] = hash_cnt, x = find(pre[match[x]]);
    std::swap(x, y);
  }
  return x;
}
 
// 代码照搬了网上的,1表示白2表示黑
// fa维护的是在哪个花
// 把白点变成黑点并入队
 
inline void blossom(int x, int y, int p) {
  while (find(x) != p) {
    pre[x] = y;
    fa[y = match[x]] = fa[x] = p;
    if (col[y] == 1)
      col[que[tail++] = y] = 2;
    x = pre[y];
  }
}
 
inline int bfs(int st) {
  for (int i = 1; i <= n; ++i)
    col[i] = 0, fa[i] = i;
  col[que[tail = 1, head = 0] = st] = 2;
  while (head != tail) {
    int x = que[head++];
    for (int y : G[x]) {
      if (!col[y]) {
        col[y] = 1, pre[y] = x;
        if (!match[y]) {
          // 增广
          while (x) {
            x = match[pre[y]];
            match[match[y] = pre[y]] = y;
            y = x;
          }
          return 1;
        } else
          col[que[tail++] = match[y]] = 2;
      } else if (col[y] == 2 && find(x) != find(y)) {
        // 奇环,缩花
        int p = get_lca(x, y);
        blossom(x, y, p);
        blossom(y, x, p);
      }
    }
  }
  return 0;
}
 
int ds[N];
bool bzzb[3020];
inline void add(int a , int b){
	bzzb[a] = bzzb[b] = 1;
    G[a].push_back(b);
    G[b].push_back(a);
}


void addedge(int x , int y, int id){
  
    if(ds[x] == 1 && ds[y] == 1){
        add(2*x,2*y);
    } else if(ds[x] == 1 && ds[y] == 2) {
        int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
        add(e1,e2);
        add(2*x,e1);
        add(2*y,e2); add(2*y-1,e2);
    } else if(ds[x] == 2 && ds[y] == 1){
        int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
        add(e1,e2);
        add(2*x,e1); add(2*x-1,e1);
        add(2*y,e2);
    } else {
        int e1 = (n+id) * 2 - 1 , e2 = e1 + 1;
        add(e1,e2);
        add(2*x,e1); add(2*x-1,e1);
        add(2*y,e2); add(2*y-1,e2);
    }
}
void clear(){
    hash_cnt = 0;
    memset(hash,0,sizeof(hash));
    memset(match,0,sizeof(match)); 
    memset(fa,0,sizeof(fa));
    memset(col,0,sizeof(col));
    memset(pre,0,sizeof(pre));
 	memset(bzzb,0,sizeof(bzzb)); 
    memset(que,0,sizeof(que));
    for (int i = 0 ; i < N ; ++i) G[i].clear();
    head = tail = 0;
}
 

 
int main() {
    using std::cin;
    while(cin >> n >> m){
        clear();
 
        int sum = 0;
        for (int i = 1 ; i <= n ; ++i){
            scanf("%d",ds+i);
            sum += ds[i];
            
            bzzb[i*2] = 1;
            if(ds[i] == 2){
            	bzzb[i*2-1] = 1;
			} 
      }
      
      for (int i = 1, x, y; i <= m; ++i) {
        scanf("%d %d", &x, &y);
        addedge(x,y,i);
      }
      int ans = 0;
      int n1 = n*2;
      n = 800;
      
      for (int i = 1; i <= 3000; ++i){
      	 if(!bzzb[i]) continue;
         if(!match[i]) bfs(i);
         
         if(match[i] && i <= n1) ans++;
         if(!match[i]) ans = 1e5;
      }
      if(ans == sum) puts("Yes");
      else puts("No");
    }
     
  return 0;
}

J Easy Integration

分部积分。



知识点回顾:
  • 后缀数组cmp方法
  • 虚树的使用,只和lca相关使用虚树
  • 增广路径每步干了些什么
  • 最大匹配和完美匹配的异同 :完美匹配一定是最大匹配,反之不成立。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值