ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ACM散题习题库4【持续更新】

2022-07-06 01:31:20  阅读:187  来源: 互联网

标签:二分 cur tx int ACM vis 散题 习题 DP


 

401. 不降子数组游戏【二分】

直接二分就行。因为getAns函数写错了,wa了几发。(当nxt[l]>r的时候,这个时候就是递增子数组,就是数组长度的组合数)

 

402. 子串(数据加强版)【组合数】

一开始想到了从两边遍历,然后把1单独拎出来,后面没想到切入口。

然后看到严gg的文章,同一段0的隔板数量应该是一样的,特判一下0作为数组开头的情况,然后计算就行了。

查看代码


void solve() {
  initAC();
  cin >> n >> str + 1;
  int l = 0, r = n + 1;
  ans = 1;
  while (l < r) {  // 不能加等号
    int pl = l + 1, pr = r - 1;
    while (pl < r && str[pl] != '1') pl++;
    while (pr > l && str[pr] != '1') pr--;
    if (pr < pl) {
      int len = (pl - pr);
	  if (l == 0) len -= 2;  
      ans = (ans * qpow(2, len) % mod);
      break;
    } else {
      int mn = min(pl - l, r - pr), mx = max(pl - l, r - pr);
      ll sum = 0;
	  if (l == 0) mn--, mx--;
      for (int i = 0; i <= mn; i++)
        sum = (sum + C(mn, i) * C(mx, i) % mod) % mod;
      ans = (ans * sum) % mod;
      l = pl, r = pr;
    }
  }
  cout << ans << endl;
}

 

403. F - String Cards (atcoder.jp)【排序 + DP】题解

感觉这一题字符串连接起来字典序最小很神奇。

(1)第一步,排序。根本就没想这种排序方法,我一开始还以为直接按字典序排序。

可能也是一个常见的结论了吧。字符串连接得到的大字符串的字典序根据拓扑图来构建是最优的。【这个结论需要记住捏】

(2)第二步,01背包。因为构建好拓扑图之后,并不能直接按照拓扑序直接选前k个,比如:(2, 1, ["b", "ba"])这组数据。或者说,拓扑序是不唯一的,直接选拓扑序的前k个得到的不一定是最小答案。

(3)‘{’的ASCII刚好是123,‘z’的ASCII是122,刚好比字母大。

查看代码
 vector<string> ss;
string dp[51];
int n, k;

bool cmp(string a, string b) { return a + b < b + a; }

void solve() {
  cin >> n >> k;
  ss.assign(n, string());
  for (int i = 0; i < n; i++) {
    cin >> ss[i];
  }
  sort(all(ss), cmp);
  dp[0] = "";
  for (int i = 1; i <= n; i++) dp[i] = "{";
  for (int i = n - 1; i >= 0; i--) {
    for (int j = min(k, n - i); j; j--) dp[j] = min(dp[j], ss[i] + dp[j - 1]);
  }
  cout << dp[k] << endl;
}

 

404. 放置多米诺骨牌 【二分图 + 黑白染色】【题解

经典的不重叠的多米诺骨牌,很自然的想到了二分图上面去(而且直方图是一个二分图)。

然后黑白染色跑最大匹配就行了,因为是直方图,黑白染色直接for一遍就行了(假设第一个点是白,那么依次向上染;然后下一列的第一个点就是黑)

 

405. 变量【贪心思维 / wqs二分暴力DP】

(1)方法1:把数组排序,然后看成n-1个数,然后选其中n-k小的数加起来就是答案。

(2)方法2:wqs二分优化DP。不知道什么原因被卡了一下。

考虑复杂度为nk的DP,然后用wqs二分优化掉后面的k。

需要注意的就是:wqs二分的时候,数值相等的时候应该选谁(在这道题里面,数值相等就选择c[i]-c[i-1]而不是添加一个新的块)。

因为我二分里面写的是cnt小于等于k,就更新答案ans,所以相同权值的情况下,cnt越小越好(就是wqs二分的那个常见问题)。

查看代码
 int n, k, c[maxn];

PLL get(ll m) {
  ll ans = m, cnt = 1;
  for (int i = 2; i <= n; i++) {
    if (m < c[i] - c[i - 1]) {
      cnt++, ans += m;
    } else {
      ans += c[i] - c[i - 1];
    }
  }
  return {ans, cnt};
}

void solve() {
  cin >> n >> k;
  for (int i = 1; i <= n; i++) cin >> c[i];
  sort(c + 1, c + 1 + n);
  ll L = 0, R = 2e10, mid;
  while (L < R) {
    mid = (L + R) >> 1;
    if (get(mid).se <= k) R = mid;
    else L = mid + 1;
  }
  cout << (get(R).fi - k * R) << endl;
}

 

405. 谁才是最终赢家?【思维 + 博弈 + 打表】【题解

不太理解严格鸽写的题解,难道两个人一定会走完所有格子吗?

然后就是用打表代码打出了规律出来,直接交了一发。。。。。

打表代码
 struct Node {
  vector<vector<int>> vis;
  int value, x, y;
  Node(int N) { x = y = value = 0, vis.assign(N, vector<int>(N, 0)); }
  void calcValue() {
    value = (1ll * x * 1919810 + y) % mod;
    int pw = 1;
    for (int i = 0; i < vis.size(); i++) {
      for (int j = 0; j < vis[i].size(); j++) {
        value = (value + pw) % mod;
        pw = (pw * 2) % mod;
      }
    }
  }
  bool operator<(const Node &rhs) const { return value < rhs.value; }
  bool operator==(const Node &rhs) const {
    if (x != rhs.x || y != rhs.y) return false;
    if (vis.size() != rhs.vis.size()) return false;
    for (int i = 0; i < vis.size(); i++) {
      if (vis[i].size() != rhs.vis[i].size()) return false;
      for (int j = 0; j < vis[i].size(); j++) {
        if (vis[i][j] != rhs.vis[i][j]) return false;
      }
    }
    return true;
  }
};

map<pair<int, Node>, int> SG;
int n = 5, x, y;

int dfs(int tp, Node x) {
  if (SG.count(make_pair(tp, x))) return SG[make_pair(tp, x)];
  // up 
  if (x.x > 0 && !x.vis[x.x - 1][x.y]) {
    int nx = x.x - 1;
    Node tx = x;
    tx.x = nx;
    tx.vis[tx.x][tx.y] = 1;
    tx.calcValue();
    if (!dfs(!tp, tx)) return true;
  }
  // down
  if (x.x < n - 1 && !x.vis[x.x + 1][x.y]) {
    int nx = x.x + 1;
    Node tx = x;
    tx.x = nx;
    tx.vis[tx.x][tx.y] = 1;
    tx.calcValue();
    if (!dfs(!tp, tx)) return true;
  }
  // left
  if (x.y > 0 && !x.vis[x.x][x.y - 1]) {
    int ny = x.y - 1;
    Node tx = x;
    tx.y = ny;
    tx.vis[tx.x][tx.y] = 1;
    tx.calcValue();
    if (!dfs(!tp, tx)) return true;
  }
  //right 
  if (x.y < n - 1 && !x.vis[x.x][x.y + 1]) {
    int ny = x.y + 1;
    Node tx = x;
    tx.y = ny;
    tx.vis[tx.x][tx.y] = 1;
    tx.calcValue();
    if (!dfs(!tp, tx)) return true;
  }
  return false;
}

void solve() {
  cin >> n >> x >> y;
  if (n & 1) {
    cout << ((x + y) & 1 ? "Alice" : "Bob") << endl;
  } else {
    cout << "Alice" << endl;
  }
  
  SG.clear();
  Node cur(n);
  // cin >> cur.x >> cur.y;
  cur.x = x, cur.y = y;
  cur.x--, cur.y--;
  cur.vis[cur.x][cur.y] = 1;
  cur.calcValue();
  cout << (dfs(0, cur) ? "Alice" : "Bob") << endl;
}

 

406. 序列中ab的个数 【概率DP + 逆向求解】【题解

不会做这道题。一开始乱分析,以为第i次操作会增加(pa)/(pa+pb)*(i-1)个ab子序列,然后输麻了。

看了题解才发现,原来要用DP来求解,而且需要逆向求解。

首先定义状态\(f[i][j]\)为当前抽的卡中有i个a,已经有j个ab子序列,那么最后的期望答案是多少。

然后思考一下,得到状态转移公式:\(f[i][j] = \frac{pa}{pa+pb}*f[i+1][j] + \frac{pb}{pa+pb}*f[i][i+j]\)

然后无穷无尽地推下去 (

当然,我们找到一个递归终止点:当i+j>=k&&j<k时,直接赋值:\(f[i][j] = i+j+pa/pb\)

具体分析以及原因,严格鸽题解里面有。

然后记忆化搜索就做完了。。。。。。好讨厌概率DP啊,每一次都不会。

 

407. 选元素(数据加强版)【暴力DP / wqs二分优化DP】

呃,说是数据加强,结果也就2500,暴力都可以过。

然后使用wqs二分+单调队列DP可以优化到\(O(n*logn)\),好耶。

一开始没想到wqs二分能做(也不太懂为什么能做------呃,直接上套路不如)

定义的DP状态为:dp[i]表示最后一个点选在i的最优答案,然后直接把dp[i]存到单调队列里面。

细节:注意单调队列,最后还要加一个while循环删掉n-k之前的才是对的。

查看代码
 int n, x, k, a[maxn];

struct Node {
  ll val, cnt;
  int id;
  bool operator>(const Node& rhs) const {
    if (val != rhs.val) return val > rhs.val;
    return cnt < rhs.cnt;
  }
};

PLL get(ll M) {
  deque<Node> q;
  q.push_back(Node{0, 0, 0});
  for (int i = 1; i <= n; i++) {
    while (q.size() && q.front().id < i - k) q.pop_front();
    ll val = q.front().val + a[i] - M, cnt = q.front().cnt + 1;
    Node cur = {val, cnt, i};
    while (q.size() && cur > q.back()) q.pop_back();
    q.push_back(cur);
  }
  while (q.size() && q.front().id <= n - k) q.pop_front();
  return {q.front().val, q.front().cnt};
}

void solve() {
  cin >> n >> k >> x;
  for (int i = 1; i <= n; i++) cin >> a[i];
  if (x * k + (k - 1) < n) {
    cout << -1 << endl;
    return;
  }
  ll L = 0, R = 2e14, mid;
  while (L < R) {
    mid = (L + R) >> 1;
    if (get(mid).se < x)
      R = mid;
    else
      L = mid + 1;
  }
  cout << (get(R).fi + x * R) << endl;
}

 

408. 序列中位数【数学 、 找规律】

(1)根据gcd(a,n)=gcd(n-a,n)=1可以知道,[1,n/2],[n/2+1,n]之间与n互质的数是镜像的。那么可以进一步得到,互质的中位数一定是小于等于n/2且与n互质的最大的那个数,然后暴力去找竟然十分快。

(2)直接打表找规律,可以找到一个4个数字为一组的规律。

 

409. 矩阵游戏【构造 + 思维 + 图论 + 二分图生成树计数】【题解

一开始以为是在n*m的矩阵中放置n+m-1个点,然后每行每列至少一个点。这样我就用容斥直接做,然后wa了。。。

看了题解之后,发现需要建图,然后完全二分图K{n,m}的生成树数量是:\(n^{m-1}*m^{n-1}\)。这个结论可以记一下。

查看代码
 int qpow(int x, int y) {
  int r = 1;
  for (; y > 0; y >>= 1, x = ((ll)x * x) % mod)
    if (y & 1) r = ((ll)r * x) % mod;
  return r;
}

int n, m;

void solve() {
  cin >> n >> m;
  cout << (1ll * qpow(n, m - 1) * qpow(m, n - 1)) % mod << endl;
}

 

410. P4552 [Poetize6] IncDec Sequence【】

 

标签:二分,cur,tx,int,ACM,vis,散题,习题,DP
来源: https://www.cnblogs.com/guanjinquan/p/16444892.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有