今天的题目太水了,但是我没有AK
【A T1:亲戚(Relatives)】
本质上是求一种对树的拓扑排序序列方案数问题,形式化地说,就是给出
m
个限制
题目中给的是若干森林然后排列,但是我们直接用0来做一个超级根,就变成树上DP的问题了。我们现在考虑对于一个根,该怎么合并他的儿子。
我们先设
f(x)
表示以
x
为根的子树里排列的方案数,
# include <cstdlib>
# include <cstdio>
using namespace std;
const int mod = 1000000007;
const int maxn = 200010;
struct Node{
Node* next;
int v;
Node(){ next = NULL; }
};
Node* G[maxn];
void insert(int u,int v){
Node* t = new Node();
t->v = v;
t-> next = G[u];
G[u] = t;
}
long long multi[maxn*2];
int n;
int fa[maxn];
long long f[maxn];
int size[maxn];
long long mod_pow(int x,int k){
if (k == 0) return 1; if (k == 1) return x;
long long t = mod_pow(x,(k>>1));
if ((k&1) == 0) return (t*t)%mod;
else return ((t*t)%mod)*x%mod;
}
inline long long inv(long long x){ return mod_pow(x,mod-2); }
void DFS(int x){
if (G[x]==NULL){
f[x] = 1;
size[x] = 1;
return;
}
DFS(G[x]->v);
f[x] = f[G[x]->v];
size[x] = size[G[x]->v];
for (Node* it=G[x]->next;it!=NULL;it=it->next){
DFS(it->v);
f[x] = ((multi[size[x]+size[it->v]]*inv(multi[size[it->v]]*multi[size[x]]%mod))%mod*f[it->v])%mod*f[x]%mod;
size[x] += size[it->v];
}
++size[x];
}
int main(){
scanf("%d",&n);
multi[0] = 1;
for (int i=1;i<=n*2+1;++i) multi[i] = multi[i-1] * i % mod;
for (int i=1;i<=n;++i){
scanf("%d",fa+i);
insert(fa[i],i);
}
++n;
DFS(0);
// for (int i=0;i<n;++i) printf("%d ",f[i]);
// printf("\n");
printf("%d\n",f[0]);
return 0;
}
【A T2 数组(Array)】
其实就是给出一个集合
S
,可以对任意
把样例推了出来之后,我就发现似乎可以贪心做。我们考虑到,不管这里的乘积是正还是负,绝对值最小的那个
a
就可以使得答案的变化最大,因为改某个数
# include <algorithm>
# include <cstdio>
# include <queue>
using namespace std;
const int mod = 1000000007;
priority_queue<long long,vector<long long>,greater<long long> > Q;
int n,k,p;
long long x;
long long ans=1;
bool isBelow0=false;
bool is0 = false;
int main(){
//freopen("array.out","w",stdout);
scanf("%d%d%d",&n,&k,&p);
p = abs(p);
for (int i=0;i<n;++i){
scanf("%lld",&x);
if (x > 0){ Q.push(x); }
else if (x == 0){ is0 = true; Q.push(x); }
else { isBelow0=!isBelow0; Q.push(-x); }
}
for (int i=0;i<k;++i){
//printf("%lld\n",Q.top());
long long d = Q.top();
Q.pop();
if (isBelow0){ Q.push(d+p); }
else{ Q.push(abs(d-p)); if((d-p)<=0) isBelow0 = true; }
}
for (int i=0;i<n;++i) { /*printf("%lld\n",Q.top());*/ ans = (ans * (Q.top() % mod)) % mod; Q.pop(); }
if (isBelow0) ans = (mod - ans);
printf("%d\n",ans);
return 0;
}
【A T3 水管(Pipe)】
实质上就是求最小生成树是否唯一,因为求MST太简单了。
这里有两种做法,第一种是我的方法,我们考虑到,任意一条原图中的
(u,v)
加到最小生成树中都会形成一个环,那么若记这个环在最小生成树上的部分中边权最大的那条是
(u′,v′)
,那么
c(u,v)≥c(u′,v′)
,否则我们将这两条边互换,也就是删去
(u′,v′)
而将
(u,v)
加入最小生成树,那么边权和更小了,与假设相矛盾。而如果我们注意到当且仅当存在一对
c(u,v)=c(u′,v′)
的时候最小生成树不唯一,就可以马上导出我的做法了。我们先求一遍最小生成树,然后用树上倍增做出任意两个点的路径上的边权最大值,接着枚举不在最小生成树中的边,就可以直接判断了,时间复杂度
O(nlgn+nα(n))
。
# include <algorithm>
# include <iostream>
# include <cstring>
# include <cstdlib>
# include <cstdio>
# include <queue>
# include <ctime>
using namespace std;
const int maxn = 100010;
const int maxm = 200010;
const int maxj = 18;
struct Node{
Node* next;
int v,c;
Node(){
next = NULL;
}
};
Node* G[maxn];
void insert(int u,int v,int c){
Node* t = new Node();
t->v = v;t->c = c; t->next = G[u];
G[u] = t;
}
struct Edge{
int u,v,c;
bool operator <(const Edge& e) const{ return c < e.c; }
};
Edge e[maxm];
bool choose[maxm];
int fa[maxn];
int find_rt(int x){
int t = x;
while (fa[t]!=t) t = fa[t];
int t1 = x,t2=fa[t1];
while (fa[t1]!=t1){
fa[t1] = t;
t1 = t2;
t2 = fa[t1];
}
return t;
}
int up[maxn][maxj];
int maxe[maxn][maxj];
int dep[maxn];
queue<int> Q;
bool vis[maxn];
void BFS(int ss){
memset(vis,0,sizeof(vis));
Q.push(ss); vis[ss] = true; dep[ss] = 0;
for (int j=0;j<maxj;++j) up[ss][j] = -1;
while (!Q.empty()){
// printf("%d\n",Q.front());
for (Node* it=G[Q.front()];it!=NULL;it=it->next){
// printf("%d\n",it->v);
if (!vis[it->v]){
// printf("(%d,%d)\n",Q.front()+1,it->v+1);
vis[it->v] = true;
dep[it->v] = dep[Q.front()]+1;
up[it->v][0] = Q.front();
maxe[it->v][0] = it->c;
for (int j=1;j<maxj;++j) up[it->v][j] = -1;
for (int j=1;j<maxj && up[up[it->v][j-1]][j-1] != -1;++j){
up[it->v][j] = up[up[it->v][j-1]][j-1];
maxe[it->v][j] = max(maxe[it->v][j-1],maxe[up[it->v][j-1]][j-1]);
}
Q.push(it->v);
}
}
Q.pop();
}
}
int find_max(int u,int v){
int ans = 0;
if (dep[u] > dep[v]) swap(u,v);
for (int j=maxj-1;j>=0;--j){
if (((dep[v]- dep[u]) & (1<<j)) != 0){ ans = max(ans,maxe[v][j]); v = up[v][j]; }
}
/*
int ans1 = ans; //BruteForce
int u1=u,v1=v;
while (u1!=v1){
ans1 = max(maxe[u1][0],ans1);
ans1 = max(maxe[v1][0],ans1);
u1 = up[u1][0];
v1 = up[v1][0];
}
*/
while (u != v){
for (int j=maxj-1;j>=0;--j){
if (up[u][j] != up[v][j] || j == 0) {
ans = max(ans,maxe[u][j]);
ans = max(ans,maxe[v][j]);
u = up[u][j];
v = up[v][j];
}
}
}
// if (ans != ans1) printf("!!!!!!!!!!!!!\n");
return ans;
}
int T,n,m;
int main(){
// freopen("pipe.in","r",stdin);
// freopen("pipe.out","w",stdout);
// double tttt = clock();
scanf("%d",&T);
while (T--){
scanf("%d%d",&n,&m);
memset(G,0,sizeof(G));
for (int i=0;i<m;++i){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
--e[i].u; --e[i].v;
}
sort(e+0,e+m);
long long ans = 0;
for (int i=0;i<n;++i) fa[i] = i;
for (int i=0;i<m;++i){
// printf("Now Deciding (%d,%d):%d\n",e[i].u+1,e[i].v+1,e[i].c);
if (find_rt(e[i].u) != find_rt(e[i].v)){
// printf("Choose!\n");
fa[find_rt(e[i].u)] = find_rt(e[i].v);
insert(e[i].u,e[i].v,e[i].c);
insert(e[i].v,e[i].u,e[i].c);
ans += e[i].c;
choose[i] = true;
}
}
cout << ans << endl;
BFS(0);
/*
for (int i=0;i<n;++i){
for (int j=0;j<maxj;++j)
printf("(%d,%d) ",(up[i][j] == -1 ? -1 : up[i][j]+1),maxe[i][j]);
printf("\n");
}
*/
bool isOnly = true;
for (int i=0;i<m;++i){
if (!choose[i] && e[i].c == find_max(e[i].u,e[i].v)){
isOnly = false;
break;
}
}
if (isOnly){ printf("Yes\n"); }
else { printf("No\n"); }
}
// cout << clock()-tttt << endl;
return 0;
}
这里有@Lanly的另一种做法,快了很多,也好写很多。我们考虑下我们求MST的时候,考虑到数据范围基本上都是用Kruskal,那么我们再次考虑下权值一样的那些边,显然如果存在一对满足条件的边在同一个环中,考虑下Kruskal的过程,对于后一条枚举的边(假设没选)我们可以判断一下和这条边同权值的已经扫过的那些边里有哪一条,是和这条边的两个端点在同一个并查集中的,那么容易看出这里就构成了个环。
不过直接枚举是显然超时的,我们可以随便搞个桶就好了,线性搞定。