• 【做题】提高组过关测试题2


    CF264C. Choosing Balls

    题意:你有(n)个球,每个都有颜色和权值(c_i)(w_i)。定义它的子序列的权值为:对于其中的每一个球,如果它在子序列中的上一个球(必须存在)与它同颜色,则贡献(a imes w_i)的权值。否则,贡献(b imes w_i)的权值。其中,(a)(b)都是常量。

    (q)次询问,每次给出(a)(b),问所有子序列中最大的权值是多少。

    (n leq 10^5, \, q leq 500)

    dp是显然的。我们记dp[c]为目前以颜色c结尾的子序列中,最大的权值。那么,我们枚举每一个球。设当前枚举到第(i)个球,就能得到

    • (dp_{c_i} '= dp_{c_i} + a imes w_i)
    • (dp_{c_i}' = max dp_k + b imes w_i \, (k ot = c_i))

    在求(max dp_k)上有一个小技巧。我们记录dp值最大的两种颜色,那么,其中至少有一个不是(c_i)。这样,对于每次询问,我们多能(O(n))地完成dp。

    时间复杂度(O(nq))

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    template <typename tp>
    inline void read(tp& x) {
      x = 0;
      char tmp;
      bool key = 0;
      for (tmp = getchar() ; !(tmp>='0'&&tmp<='9') ; tmp=getchar())
        key = (tmp == '-');
      for ( ; tmp >= '0' && tmp <= '9' ; tmp = getchar())
        x = (x<<1) + (x<<3) + tmp - '0';
      if (key) x = -x;
    }
    const int N = 100010, INF = 0x3f3f3f3f3f3f3f3f;
    int n,q,dp[N],c[N],val[N],a,b,mx,mx1,pm,pm1,vis[N];
    signed main() {
      read(n), read(q);
      for (int i = 1 ; i <= n ; ++ i)
        read(val[i]);
      for (int i = 1 ; i <= n ; ++ i)
        read(c[i]);
      for (int i = 1 ; i <= q ; ++ i) {
        read(a), read(b);
        for (int j = 1 ; j <= n ; ++ j)
          dp[j] = 0, vis[j] = 0;
        mx = mx1 = 0;
        pm = pm1 = 0;
        for (int j = 1 ; j <= n ; ++ j) {
          int x = -INF,y = -INF,z = -INF,v;
          if (pm != c[j])
    	x = b * val[j] + mx;
          if (pm1 != c[j])
    	y = b * val[j] + mx1;
          if (vis[c[j]])
    	z = val[j] * a + dp[c[j]];
          v = max(x,y); v = max(v,z);
          if (!vis[c[j]]) dp[c[j]] = v;
          else dp[c[j]] = max(dp[c[j]],v);
          vis[c[j]] = 1;
          if (v == dp[c[j]]) {
    	if (pm == c[j]) mx = v;
    	else if (v > mx) {
    	  mx1 = mx;
    	  pm1 = pm;
    	  mx = v;
    	  pm = c[j];
    	} else if (v > mx1) {
    	  mx1 = v;
    	  pm1 = c[j];
    	}
          }
        }
        int ans = 0;
        for (int j = 1 ; j <= n ; ++ j)
          if (vis[j]) ans = max(ans,dp[j]);
        cout << ans << endl;
      }
      return 0;
    }
    

    CF212C. Cowboys

    题意:(n)个人站成一个圆圈。每个人要么面向顺时针,要么面向逆时针。前者用A来表示,后者用B表示。如果两个人面对面地站着,那么,在下一秒他们都对改变自己面朝的方向。给出一秒后的状态,问在这一秒有多少种可能的站法。

    (n leq 100)

    考虑dp。令dp[i,a]为放到第(i)位且第(i)位是(a)的方案数。分类讨论一下就能转移了。

    此外,还要讨论环的开头和结尾是否面对面。

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N = 110;
    int dp[N][2],n,val[N],ans;
    char s[N];
    signed main() {
      scanf("%s",s+1);
      n = strlen(s+1);
      for (int i = 1 ; i <= n ; ++ i)
        val[i] = (s[i] == 'A');
      dp[1][val[1]] = 1;
      dp[0][0] = 1;
      for (int i = 2 ; i <= n ; ++ i) {
        if (val[i]) {
          dp[i][1] += dp[i-1][0] + dp[i-1][1];
          if (!val[i-1] && i != 2) dp[i][0] += dp[i-2][0] + dp[i-2][1];
        } else dp[i][0] += dp[i-1][0];
        // printf("%lld:(%lld,%lld)
    ",i,dp[i][0],dp[i][1]);
      }
      if (val[1]) ans += dp[n][0] + dp[n][1];
      else {
        ans += dp[n][0];
        if (val[2]) {
          memset(dp,0,sizeof dp);
          dp[2][0] = 1;
          for (int i = 3 ; i <= n ; ++ i) {
    	if (val[i]) {
    	  dp[i][1] += dp[i-1][0] + dp[i-1][1];
    	  if (!val[i-1] && i != 2) dp[i][0] += dp[i-2][0] + dp[i-2][1];
    	} else dp[i][0] += dp[i-1][0];
          }
          ans += dp[n][0] + dp[n][1];
        }
      }
      memset(dp,0,sizeof dp);
      if (val[1] == 1 && val[n] == 0) {
        dp[1][0] = 1;
        for (int i = 2 ; i < n ; ++ i) {
          if (val[i]) {
    	dp[i][1] += dp[i-1][0] + dp[i-1][1];
    	if (!val[i-1]) dp[i][0] += dp[i-2][0] + dp[i-2][1];
          } else dp[i][0] += dp[i-1][0];
        }
        ans += dp[n-1][0] + dp[n-1][1];
      }
      cout << ans << endl;
      return 0;
    }
    

    CF160D. Edges in MST

    题意:有一个(n)个点,(m)条边的联通图,边有边权。求每一条边与这个图的最小生成树的关系(在所有最小生成树中;在某些最小生成树中;不在任何最小生成树中)。

    (n, m leq 10^5)

    考虑kruskal的运算过程。那么,我们显然要对边权相等的边合起来考虑。

    假设当前有若干个联通块,则连接同一个联通块的边,显然不会在任何最小生成树中。(加入它就会形成环,且环上其他边的边权都小于它)

    剩下的边至少出现在一个最小生成树中。若某条边出现在所有最小生成树中,则加入所有权值小于等于它的边后,它不在任何环上。也就是说,它是桥。因此,我们把目前的联通块缩点,加入这些边后用targan判断是否是桥就可以了。这里还要特判重边。

    时间复杂度(O(n log n))

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100010;
    struct edge {
      int a,b,v,id;
      bool operator < (const edge& x) const {
        return v < x.v;
      }
    } ed[N];
    int n,uni[N],m,ans[N];
    vector<int> recv,rece;
    multiset<pair<int,int> > st;
    struct cont {
      int la,b;
    } con[N << 1];
    int tot,fir[N];
    void add(int from,int to) {
      con[++tot] = (cont) {fir[from],to};
      fir[from] = tot;
    }
    int dfn[N],low[N],top,sta[N],col[N],ccnt;
    void dfs(int pos,int fa) {
      sta[low[pos] = dfn[pos] = ++top] = pos;
      for (int i = fir[pos] ; i ; i = con[i].la) {
        if (col[con[i].b] || con[i].b == fa) continue;
        if (!dfn[con[i].b]) {
          dfs(con[i].b,pos);
          low[pos] = min(low[con[i].b],low[pos]);
        } else low[pos] = min(low[pos],dfn[con[i].b]);
      }
      if (low[pos] == dfn[pos]) {
        ++ ccnt;
        while (top >= dfn[pos])
          col[sta[top--]] = ccnt;
      }
    }
    void init() {
      for (int i = 0 ; i < (int)recv.size() ; ++ i) {
        int pos = recv[i];
        fir[pos] = dfn[pos] = low[pos] = col[pos] = 0;
      }
      recv.clear();
      tot = ccnt = top = 0;
      rece.clear();
    }
    int get_fa(int pos) {
      return uni[pos] != pos ? uni[pos] = get_fa(uni[pos]) : pos;
    }
    int main() {
      int x,y,z;
      scanf("%d%d",&n,&m);
      for (int i = 1 ; i <= m ; ++ i) {
        scanf("%d%d%d",&x,&y,&z);
        ed[i] = (edge) {x,y,z,i};
      }
      sort(ed+1,ed+m+1);
      for (int i = 1 ; i <= n ; ++ i)
        uni[i] = i;
      for (int i = 1 ; i <= m ; ++ i) {
        int tmp = ed[i].v, rec = i;
        st.clear();
        for ( ; i <= m && ed[i].v == tmp ; ++ i) {
          if (get_fa(ed[i].a) == get_fa(ed[i].b))
    	ans[ed[i].id] = -1;
          else {
    	int x = get_fa(ed[i].a), y = get_fa(ed[i].b);
    	if (x > y) swap(x,y);
    	st.insert(make_pair(x,y));
    	recv.push_back(x);
    	recv.push_back(y);
          }
        }
        i --;
        for (int j = rec ; j <= i ; ++ j) {
          if (get_fa(ed[j].a) != get_fa(ed[j].b)) {
    	int x = get_fa(ed[j].a), y = get_fa(ed[j].b);
    	if (x > y) swap(x,y);
    	add(x,y);
    	add(y,x);
    	if (st.count(make_pair(x,y)) > 1) {
    	  ans[ed[j].id] = 2;
    	} else rece.push_back(j);
          }
        }
        for (int j = 0 ; j < (int)recv.size() ; ++ j)
          if (!dfn[recv[j]]) dfs(recv[j],0);
        for (int j = 0 ; j < (int)rece.size() ; ++ j) {
          int k = rece[j];
          if (col[get_fa(ed[k].a)] != col[get_fa(ed[k].b)])
    	ans[ed[k].id] = 1;
          else ans[ed[k].id] = 2;
        }
        init();
        for ( ; rec <= i ; ++ rec) {
          int x = ed[rec].a, y = ed[rec].b;
          x = get_fa(x), y = get_fa(y);
          if (x != y)
    	uni[x] = y;
        }
      }
      for (int i = 1 ; i <= m ; ++ i) {
        if (ans[i] == -1) puts("none");
        else if (ans[i] == 1) puts("any");
        else if (ans[i] == 2) puts("at least one");
        else assert(0);
      }
      return 0;
    }
    

    CF372B. Counting Rectangles is Fun

    题意:给出一个(n imes m)的01矩阵,有(q)次询问,问它的一个子矩阵中有多少个不包含1的子矩阵。

    (n,m leq 40, \, q leq 3 imes 10^5)

    关键就在于一个小技巧,预处理时,我们枚举所有子矩阵,只计算出底与它的底在同一条直线上的子矩阵个数。对这个做前缀和就能得到答案。而通过使用单调栈,很容易把预处理复杂度优化到(O(n^5))(O(n^4))

    时间复杂度(O(n^5 + m))

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 45;
    int a[N][N],n,m,q,top,cur,ans[N][N][N][N];
    char tmp[N];
    struct data {
      int l,r,v;
    } sta[N];
    int main() {
      int x,y,z,d;
      scanf("%d%d%d",&n,&m,&q);
      for (int i = 1 ; i <= n ; ++ i) {
        scanf("%s",tmp+1);
        for (int j = 1 ; j <= m ; ++ j)
          a[i][j] = tmp[j] - '0';
      }
      for (int i = 1 ; i <= n ; ++ i)
        for (int j = i ; j <= n ; ++ j) {
          for (int k = 1 ; k <= m ; ++ k) {
    	top = 0;
    	cur = 0;
    	for (int p = k ; p <= m ; ++ p) {
    	  int tmp = j - i + 1;
    	  for (int t = i ; t <= j ; ++ t)
    	    if (a[t][p] == 1) tmp = j - t;
    	  data tp = (data) {p,p,tmp};
    	  while (sta[top].v >= tp.v && top) {
    	    tp.l = sta[top].l;
    	    cur -= (sta[top].r - sta[top].l + 1) * sta[top].v;
    	    top --;
    	  }
    	  sta[++top] = tp;
    	  cur += (tp.r - tp.l + 1) * tp.v;
    	  ans[i][j][k][p] = ans[i][j][k][p-1] + cur;
    	}
          }
        }
      for (int i = 1 ; i <= n ; ++ i)
        for (int k = 1 ; k <= m ; ++ k)
          for (int p = k ; p <= m ; ++ p)
    	for (int j = i ; j <= n ; ++ j)
    	  ans[i][j][k][p] += ans[i][j-1][k][p];
      for (int i = 1 ; i <= q ; ++ i) {
        scanf("%d%d%d%d",&x,&y,&z,&d);
        printf("%d
    ",ans[x][z][y][d]);
      }
      return 0;
    }
    

    CF510E. Fox And Dinner

    题意:有(n)个元素,每个的权值为(a_i)。你需要把它们分成若干组,每一组的元素排成一个环,且满足:

    • 每一组都至少有3个元素。
    • 每一组的环上,每一对相邻元素的权值和为奇质数。

    要求判断是否有解,若有解则输出任意一组解。

    (n leq 200, \, a_i leq 10^4)

    显然,两个元素如果相加为奇数,那么一定是一奇一偶。因此,我们可以把元素按奇偶性分组,然后依照相加能否为奇质数连边。这样,问题就变成了二分图的环覆盖。

    设一个结点在二分图上的度数为与它匹配的边数。考虑一个点如果在环上,那么它的度数为2。可以发现,存在方案使二分图上的每一个结点度数为2,等价于存在一个环覆盖的方案。这个结论必要性显然,而在充分性上,若有结点点不在环上,则一定存在结点的度数小于2;若有结点在多个环上,则一定存在结点的度数大于2。

    让每个点的度数都变成2,这个可以用最大流解决。至于输出方案,只要在图上dfs就可以了。

    时间复杂度(O(n^4 + m)),其中(m)(a_i)的权值范围。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 610, MAX = 20000, INF = 0x3f3f3f3f;
    struct edge {
      int la,b,cap;
    } con[N * N];
    int tot=1,fir[N];
    void add(int from,int to,int capc) {
      con[++tot] = (edge) {fir[from],to,capc};
      fir[from] = tot;
      con[++tot] = (edge) {fir[to],from,0};
      fir[to] = tot;
    }
    int cur[N], dis[N], n, st, en, vis[N];
    int dfs(int pos,int imp) {
      if (pos == en || (!imp)) return imp;
      int expo = 0, tmp;
      for (int &i = cur[pos] ; i ; i = con[i].la) {
        if (dis[con[i].b] == dis[pos] + 1) {
          tmp = dfs(con[i].b,min(imp,con[i].cap));
          con[i].cap -= tmp;
          con[i^1].cap += tmp;
          expo += tmp;
          imp -= tmp;
          if (!imp) break;
        }
      }
      return expo;
    }
    bool bfs() {
      static queue<int> q;
      while (!q.empty()) q.pop();
      memset(dis,0,sizeof dis);
      for (int i = 1 ; i <= n ; ++ i)
        cur[i] = fir[i];
      dis[st] = 1;
      q.push(st);
      for (int pos ; !q.empty() ; q.pop()) {
        pos = q.front();
        for (int i = fir[pos] ; i ; i = con[i].la) {
          if (con[i].cap && (!dis[con[i].b])) {
    	dis[con[i].b] = dis[pos] + 1;
    	q.push(con[i].b);
          }
        }
      }
      if (!dis[en]) return 0;
      return 1;
    }
    int a[N],isp[MAX + 10], pri[MAX], pcnt, ans, cnt;
    vector<int> rec[N];
    void prework() {
      for (int i = 2 ; i <= MAX ; ++ i) {
        if (!isp[i]) pri[++pcnt] = i;
        for (int j = 1 ; j <= pcnt && pri[j] * i <= MAX ; ++ j) {
          isp[pri[j] * i] = 1;
          if (i % pri[j] == 0) break;
        }
      }
    }
    void sdf(int pos,int fa) {
      rec[cnt].push_back(pos);
      vis[pos] = 1;
      for (int i = fir[pos] ; i ; i = con[i].la) {
        if (con[i].b <= n-2 && ((con[i].cap == 0 && (i&1) == 0) || (con[i].cap == 1 && (i&1) == 1))) {
          if (con[i].b == fa || vis[con[i].b]) continue;
          sdf(con[i].b,pos);
        }
      }
    }
    int main() {
      scanf("%d",&n);
      for (int i = 1 ; i <= n ; ++ i)
        scanf("%d",&a[i]);
      st = ++n;
      en = ++n;
      prework();
      for (int i = 1 ; i <= n-2 ; ++ i) {
        if (a[i]&1) {
          add(st,i,2);
          for (int j = 1 ; j <= n-2 ; ++ j)
    	if (!isp[a[i] + a[j]]) add(i,j,1);
        } else add(i,en,2);
      }
      while (bfs())
        ans += dfs(st,INF);
      if (ans < n - 2) puts("Impossible");
      else {
        for (int i = 1 ; i <= n - 2 ; ++ i) if (!vis[i]) {
    	++ cnt;
    	sdf(i,0);
          }
        printf("%d
    ",cnt);
        for (int i = 1 ; i <= cnt ; ++ i) {
          printf("%d ",(int)rec[i].size());
          for (int j = 0 ; j < (int)rec[i].size() ; ++ j)
    	printf("%d ",rec[i][j]);
          puts("");
        }
      }
      return 0;
    }
    

    小结:总会卡在几个小技巧上……应该是做题经验不够丰富。
  • 相关阅读:
    虚拟机类加载机制详解
    简单了解Tomcat与OSGi的类加载器架构
    Java高并发编程(四)
    Java高并发编程(三)
    Java高并发编程(一)
    垃圾收集与几种常用的垃圾收集算法
    初识java内存区域
    vue2.0基础学习(1)
    git/github 生成密钥
    手机预览vue项目
  • 原文地址:https://www.cnblogs.com/cly-none/p/9299353.html
Copyright © 2020-2023  润新知