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 imes 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
",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;
}
小结:其实题目都不难,但却时常被卡住。这说明自己思维还不够灵活。