【做题】提高组过关测试题1

CF980E. The Number Games

题意:有一棵含有\(n\)个结点的树。求所有含有\(n-k\)个结点的联通块中,结点编号从大到小排序,字典序最大的联通块。
\(n \leq 10^6\)

显然可以贪心。按编号从大到小枚举结点,能加入联通块的就一定加入联通块。我们以\(n\)号点为根结点,每次就是把一个结点到根的路径上所有点加入联通块。当然,其中有一些结点已经加入联通块了。
考虑到每个结点最多被加入联通块1次,我们可以在加入时暴力。至于判断一个结点是否可以加入,用倍增查找它到根的路径上深度最大的已经被加入联通块的结点,我们就能得出要加入这个点,同时还要加入多少个结点。
时间复杂度\(O(n \log n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010, MP = 22;
struct edge {
  int la,b;
} con[N << 1];
int tot,fir[N];
void add(int from,int to) {
  con[++tot] = (edge) {fir[from],to};
  fir[from] = tot;
}
int vis[N],fa[N],anc[N][MP],dep[N],n,k,cur;
void dfs(int pos) {
  anc[pos][0] = fa[pos];
  dep[pos] = dep[fa[pos]] + 1;
  for (int i = 1 ; i < MP ; ++ i) {
    anc[pos][i] = anc[anc[pos][i-1]][i-1];
    if (!anc[pos][i]) break;
  }
  for (int i = fir[pos] ; i ; i = con[i].la) {
    if (con[i].b == fa[pos]) continue;
    fa[con[i].b] = pos;
    dfs(con[i].b);
  }
}
int query(int x) {
  int t = x;
  for (int i = MP - 1 ; i >= 0 ; -- i)
    if (!vis[anc[x][i]])
      x = anc[x][i];
  x = fa[x];
  return dep[t] - dep[x];
}
int main() {
  int a,b;
  scanf("%d%d",&n,&k);
  for (int i = 1 ; i < n ; ++ i) {
    scanf("%d%d",&a,&b);
    add(a,b);
    add(b,a);
  }
  dfs(n);
  vis[0] = 1;
  vis[n] = cur = 1;
  k = n - k;
  for (int i = n - 1 ; i >= 1 ; -- i) if (!vis[i]) {
    if (cur + query(i) <= k) {
      int pos = i;
      while (!vis[pos]) {
	vis[pos] = 1;
	++ cur;
	pos = fa[pos];
      }
    }
  }
  for (int i = 1 ; i <= n ; ++ i)
    if (!vis[i]) printf("%d ",i);
  puts("");
  return 0;
}

CF512B. Fox And Jumping

题意:有\(n\)个卡片,每个卡片都有一个权值\(l_i\)与费用\(c_i\)。你需要从其中购买若干张卡片,使得它们权值的gcd为1。求最小的费用。若无解,输出-1。
\(n \leq 300, \, l_i \leq 10^9\)

问题就在于在\(l_i\)范围内的质数太多了。
然后有一个巧妙的小技巧,因为一个数的互不相同的质因子个数是很少的,所以我们枚举选取的第一张卡片。我们dp记录状态时只要考虑它所包含的质数就行了。
时间复杂度\(O(n^2 \times 2^w)\),其中\(w\)为最多含有的互不相同的质因子个数,且\(w \leq 10\)

#include <bits/stdc++.h>
using namespace std;
const int N = 310, M = 1 << 10, MAX = 100000, INF = 0x3f3f3f3f;
int isp[MAX+10],pri[MAX],pcnt,n,l[N],c[N],dp[M],ans=INF;
vector<int> fac[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;
    }
  }
}
int main() {
  scanf("%d",&n);
  for (int i = 1 ; i <= n ; ++ i)
    scanf("%d",&l[i]);
  for (int i = 1 ; i <= n ; ++ i)
    scanf("%d",&c[i]);
  prework();
  for (int i = 1 ; i <= n ; ++ i) {
    int tmp = l[i];
    for (int j = 1 ; j <= pcnt ; ++ j)
      if (tmp % pri[j] == 0) {
	fac[i].push_back(pri[j]);
	while (tmp % pri[j] == 0) tmp /= pri[j];
      }
    if (tmp != 1) fac[i].push_back(tmp);
  }
  先手 i = 1 ; i <= n ; ++ i) {
    int tot = (1 << fac[i].size());
    memset(dp,0x3f,sizeof dp);
    dp[tot-1] = c[i];
    for (int j = i + 1 ; j <= n ; ++ j) {
      int tmp = 0;
      for (int k = 0 ; k < (int)fac[i].size() ; ++ k)
	if (l[j] % fac[i][k] == 0) tmp |= (1 << k);
      for (int k = 0 ; k < tot ; ++ k)
	dp[k & tmp] = min(dp[k & tmp],dp[k] + c[j]);
    }
    ans = min(ans,dp[0]);
  }
  if (ans == INF) puts("-1");
  else printf("%d\n",ans);
  return 0;
}

CF455B. A Lot of Games

题意:A和B在玩一个博弈游戏。他们有若干个个长度总和不大于\(l\)的字符串。在它们建成的trie树上,从根结点开始,两个人轮流移动同一个棋子,不断向它的儿子结点走。先走到叶结点的人输。他们连续玩\(k\)轮游戏,第\(i-1\)局输的人在第\(i\)局先手。他们的目的都是在第\(k\)局取胜。问都采取最有决策下,谁能取胜。
\(l \leq 10^5, \, k <= 10^9\)

关键在于一个细节,为了在第\(k\)局取胜,可以故意在某一局输掉以换取先手权。因此,我们在树上dp时,要记录在每个结点先手的可能情况:

  • 只能赢。
  • 只能输。
  • 由自己决定赢或输。
  • 由对手决定赢或输。

具体细节请自行讨论。
时间复杂度\(O(l)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int ch[N][26], dp[N], n, k, l, cnt = 1, sz[N];
char tmp[N];
void dfs(int pos) {
  int key = 0;
  dp[pos] = -1;
  for (int i = 0 ; i < 26 ; ++ i) if (ch[pos][i]) {
    dfs(ch[pos][i]);
    key = 1;
    switch (dp[ch[pos][i]]) {
    case 1:if (dp[pos] == -1) dp[pos] = 2;
      if (dp[pos] == 1) dp[pos] = 3; break;
    case 2:if (dp[pos] == -1) dp[pos] = 1;
      if (dp[pos] == 2) dp[pos] = 3; break;
    case -1: dp[pos] = 3;
    }
  }
  if (key == 0)
    dp[pos] = 1;
  if (pos == 1 && dp[pos] == 1) dp[pos] = 2;
  else if (pos == 1 && dp[pos] == 2) dp[pos] = 1;
}
int main() {
  scanf("%d%d",&n,&k);
  for (int i = 1 ; i <= n ; ++ i) {
    scanf("%s",tmp+1);
    l = strlen(tmp+1);
    for (int j = 1, p = 1 ; j <= l ; ++ j) {
      if (!ch[p][tmp[j] - 'a'])
	ch[p][tmp[j] - 'a'] = ++cnt;
      p = ch[p][tmp[j] - 'a'];
    }
  }
  dfs(1);
  k = k&1;
  if (dp[1] == 3) puts("First");
  else if (dp[1] == 2) puts("Second");
  else if (dp[1] == 1) {
    if (k) puts("First");
    else puts("Second");
  } else puts("Second");
  return 0;
}

小结:其实题目都不难,但却时常被卡住。这说明自己思维还不够灵活。
posted @ 2018-07-06 16:14  莫名其妙的aaa  阅读(188)  评论(0编辑  收藏  举报