题目链接:https://codeforces.com/contest/615
吐槽:看来cf的比赛round数和contest序号也是有逆序的。
A - Bulbs
不能叫做题。
set<int> S;
void test_case() {
int m, n;
scanf("%d%d", &m, &n);
while(m--) {
int t;
scanf("%d", &t);
while(t--) {
int x;
scanf("%d", &x);
S.insert(x);
}
}
if(S.size() == n)
puts("YES");
else
puts("NO");
}
B - Longtail Hedgehog
吐槽:Hedgehog是刺猬的意思。我仔细看了很多遍,都觉得有歧义,我都觉得刺只需要长在尾巴两端的其中一端就可以了。所以我以为像是一个两头刺的链。事实上这里只准一头长刺。
题意:有一个 (n) 个点 (m) 条边的无向图。给边染色,染红色的叫做“尾巴”,尾巴必须是一条链,且点的序号是单调的。然后把尾巴的其中一头的所有关联边(包括尾巴)都染黑色(意思是有一条边可以同时红和黑),叫做“刺”。一个刺猬图的好看程度就是尾巴的长度(定义为节点的数量)和刺的数量的乘积。
题意:那么可以按照单调性来构造,按节点编号逐个枚举新的一段尾巴,那么有两种情况,一种是选择新的节点作为刺,另一种是保持原状。被剧透了只能dp了。不过由于单调性确实dp是自然的。状态自己设计呗。
假如长刺的端始终是大节点:
设 (dp[i]) 表示以i作为尾巴的长刺端的最大值。
设 (len[i]) 表示以i作为尾巴的一端的最长的尾巴的长度。
那么存在转移:
(len[i]=maxlimits_{vin G[i] , v<i}len[v]+1)
(dp[i]=deg[i]*len[i])
所以就不需要 (dp[i]) 了。
假如长刺的端始终是小节点,那么只需要枚举这个小节点的时候看看最长能延伸多远,那看起来就反过来dp一次(从大节点向小节点连)就可以了,具体来说就是:
设 (dp[i]) 表示以i作为尾巴的长刺端的最大值。
设 (len[i]) 表示以i作为尾巴的一端的最长的尾巴的长度。
那么存在转移:
(len[i]=maxlimits_{vin G[i] , v>i}len[v]+1)
(dp[i]=deg[i]*len[i])
交上去错了,原来不能加在小的那头,看来英语也有歧义。
int n, m;
vector<int> G[100005];
int deg[100005];
int len[100005];
void test_case() {
int n, m;
scanf("%d%d", &n, &m);
while(m--) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i = 1; i <= n; ++i)
deg[i] = G[i].size();
for(int i = 1; i <= n; ++i)
sort(G[i].begin(), G[i].end());
ll ans = 0;
for(int i = 1; i <= n; ++i) {
len[i] = 1;
for(auto &v : G[i]) {
if(v > i)
break;
len[i] = max(len[i], len[v] + 1);
}
//printf("len=%d
", len[i]);
//printf("deg=%d
", deg[i]);
ans = max(ans, 1ll * deg[i] * len[i]);
}
/*for(int i = 1; i <= n; ++i)
reverse(G[i].begin(), G[i].end());
for(int i = n; i >= 1; --i) {
len[i] = 1;
for(auto &v : G[i]) {
if(v < i)
break;
len[i] = max(len[i], len[v] + 1);
}
printf("len=%d
", len[i]);
printf("deg=%d
", deg[i]);
ans = max(ans, 1ll * deg[i] * len[i]);
}*/
printf("%lld
", ans);
}
谁规定endpoint就是结束点啊,翻译成端点不行吗?
D - Multipliers
题意:给一个超超超大的数 (x) 的质因数分解,求 (prodlimits_{d|x}d) 。
题解:假如是求 (sumlimits_{d|x}d) 就很好做,因为这个是积性函数。但是方法肯定是类似的,先观察一个复杂一点的: (30=2cdot 3cdot 5) ,因子为: ({1,2,3,5,6,10,15,30}) ,他们的分解为: ({1,2,3,5,2cdot 3,2cdot 5,3cdot 5,2cdot 3cdot 5}) ,由此可以猜想某种质因子的幂贡献的次数为除去这种质因子之后的因数数量。由于每次贡献是用积的方式贡献,所以统计的时候用快速幂,那么指数就理所当然用欧拉定理。
再看一个 (60=2^2cdot 3cdot 5) ,因子为: ({1,2,3,4,5,6,10,12,15,20,30,60}) ,他们的分解为: ({1,2,3,2^2,5,2cdot 3,2cdot 5,2^2cdot3,3cdot 5,2^2cdot5,2cdot 3cdot 5,2^2cdot 3cdot 5})
最简单的方法是维护一个前缀积和后缀积,这样不需要用扩展欧几里得来算逆元。
int n, p[200005];
int cnt[200005];
ll preprodc[200005];
ll sufprodc[200005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &p[i]);
++cnt[p[i]];
}
sort(p + 1, p + 1 + n);
int c = unique(p + 1, p + 1 + n) - (p + 1);
preprodc[0] = 1;
for(int i = 1; i <= c; ++i)
preprodc[i] = (preprodc[i - 1] * (cnt[p[i]] + 1)) % (MOD - 1);
sufprodc[c + 1] = 1;
for(int i = c; i >= 1; --i)
sufprodc[i] = (sufprodc[i + 1] * (cnt[p[i]] + 1)) % (MOD - 1);
ll prod = 1;
for(int i = 1; i <= c; ++i) {
ll pk = 1, a = (preprodc[i - 1] * sufprodc[i + 1]) % (MOD - 1);
for(int j = 1; j <= cnt[p[i]]; ++j) {
pk = (pk * p[i]) % MOD;
prod = (prod * qpow(pk, a)) % MOD;
}
}
printf("%lld
", prod);
}
总结:那么这道题告诉一个新规律:
记 (f(x)=prodlimits_{d|x}d)
对于质因数的幂次 (p^k)
(g(p^k)=prodlimits_{i=1}^{k}p^i=p^{frac{k(1+k)}{2}})
那么 (f(x)=f(p_1^{k_1}p_2^{k_2}...p_t^{k_t})=prodlimits_{i=1}^{t}p_i^{frac{(k_i+1)k_i}{2}sigma_0(frac{x}{p_i^{k_i}})})
ll prod = 1;
for(int i = 1; i <= c; ++i) {
int k = cnt[p[i]];
ll sigma0 = (preprodc[i - 1] * sufprodc[i + 1]) % (MOD - 1);
ll t = (1ll * (k + 1) * k / 2) % (MOD - 1) * sigma0 % (MOD - 1);
prod = (prod * qpow(p[i], t)) % MOD;
}
printf("%lld
", prod);
思考:尝试用扩展欧几里得去做,但是事实上不一定存在逆元,因为模数并不是质数。