心得
实质上的,第一次三人合作上机
暴露出很多不足,多练才能变强
D(NTT)E(dfs+高维前缀和)G(dfs序)J(生成函数+FFT)待补
思路来源
https://blog.csdn.net/qq_31759205/article/details/76154626 H题
https://blog.csdn.net/ME495/article/details/76165039 I题
https://www.cnblogs.com/chen9510/p/7258595.html?utm_source=itdadao&utm_medium=referral L题
https://blog.csdn.net/sunsiyou/article/details/97299155 待补题
赛后补题
HDU 6035 Colorful Tree(树形dp)
最多50组样例,每次给定n(n<=2e5)个点的一棵树,点i有一种颜色ci(1<=ci<=n)
无序点对(u,v)的贡献,定义为u到v这条路径(含u和v)上不同颜色的种数
求所有无序点对的贡献之和
先假设每条路径都出现了n种颜色,再减去每条路径没出现过的颜色
单独考虑一种颜色c,则c把这棵树的其余区域划分成了不同块,
每个块内都没有出现c,所以应当减去的是(x为每个块的大小)
考虑dfs的过程,开始u的贡献认为是子树v,后来v减去了所有u的子树之后的x为这块的真实sz
特别地,如果c没有在这棵树中出现,sz显然为n,这棵树的大小
有一遍dfs的写法,树形dp还是不怎么熟练
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=2e5+10;
vector<int>E[N];
int c[N],u,v;
int n,sz[N],num[N];
ll ans;
ll C(ll x)
{
return 1ll*x*(x-1)/2;
}
void dfs(int u,int fa)
{
sz[u]=1;
for(int v:E[u])
{
if(v==fa)continue;
dfs(v,u);
sz[u]+=sz[v];
}
}
void dfs2(int u,int fa)
{
int tmp=num[c[u]];
for(int v:E[u])
{
if(v==fa)continue;
num[c[u]]=sz[v];
dfs2(v,u);
ans-=C(num[c[u]]);
}
num[c[u]]=tmp-sz[u];
}
int main()
{
int ca=1;
while(~scanf("%d",&n))
{
for(int i=1;i<=n;++i)
{
E[i].clear();
num[i]=sz[i]=0;
}
for(int i=1;i<=n;++i)
{
scanf("%d",&c[i]);
}
for(int i=1;i<n;++i)
{
scanf("%d%d",&u,&v);
E[u].pb(v),E[v].pb(u);
}
dfs(1,-1);
for(int i=1;i<=n;++i)
num[i]=sz[1];
ans=C(n)*n;
dfs2(1,-1);
for(int i=1;i<=n;++i)
ans-=C(num[i]);
printf("Case #%d: %lld\n",ca++,ans);
}
return 0;
}
HDU 6038 Function(置换群 循环节)
多组样例,保证n和m总和不超过1e6
每组样例,给出一个n和m(1<=n,m<=1e5),
给出长度为n的a[],0<=ai<=n-1,下标为0到n-1
给出长度为m的b[],0<=bj<=m-1,下标为0到m-1
输出不同的映射f数量,对于每个i,都满足,方案模1e9+7
手跑样例可以发现,置换群是一个个独立的环,
n个点n条边,所以必有至少一个环;去掉这一个环,点边仍相等,所以皆为环
b中的数对应一个个长度的环cyc1,而映射关系对应a中的数的环cyc2,
应满足cyc2是cyc1的倍数,起点从cyc1的对应的|cyc1|个数中任取一个
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+10;
int ca,n,m,a[N],b[N];
bool vis[N],vis2[N];
int cyc[N];//cyc[i]:存在长度为i的循环节cyc[i]个
vector<int>ans;
void add(ll &x,ll y)
{
x+=y;
if(x<0)x+=mod;
if(x>=mod)x-=mod;
}
void init()
{
for(int i=0;i<=m;++i)
cyc[i]=vis[i]=0;
for(int i=0;i<=n;++i)
vis2[i]=0;
ans.clear();
}
void solve()
{
ll res=1;
for(int i=0;i<m;++i)
{
if(vis[i])continue;
int cnt=0;
for(int j=i;!vis[j];j=b[j])
{
cnt++;
vis[j]=1;
}
cyc[cnt]++;
//printf("cyc:%d\n",cnt);
}
for(int i=0;i<n;++i)
{
if(vis2[i])continue;
int cnt=0;
for(int j=i;!vis2[j];j=a[j])
{
cnt++;
vis2[j]=1;
}
ans.push_back(cnt);
}
for(int v:ans)
{
//printf("v:%d\n",v);
ll tmp=0;
for(int j=1;j*j<=v;++j)
{
if(v%j==0)
{
add(tmp,1ll*cyc[j]*j%mod);
if(j!=v/j)add(tmp,1ll*cyc[v/j]*(v/j)%mod);
}
}
//printf("tmp:%lld\n",tmp);
res=res*tmp%mod;
}
printf("Case #%d: %lld\n",++ca,res);
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=0;i<n;++i)
scanf("%d",&a[i]);
for(int i=0;i<m;++i)
scanf("%d",&b[i]);
solve();
init();
}
return 0;
}
HDU 6040 Hints of sd0061(nth_element)
多组样例,约15组,
每组样例,给出一个n(n<=1e7),由rng61()生成长度为n的数组a[],
m(m<=100)个询问,第j次询问数组a[]中从小到大rank=bj的值
允许离线,保证对于任意bi,bj,bk,若bi<bk,bj<bk,则bi+bj<=bk
STL中nth_element函数的应用,其实就是快排的思想,
每次找到轴中值之后,只搜另一半,确定这个数的rank期望意义下,复杂度O(n)
nth_element(start,rank,end),三个参数都为a+偏移量的形式
b给定的形式,类似斐波那契数列,bj<=bk/2,保证排序之后至少降一半
所以对于连续增序三元组(i,j,k),在[b,b+k)里询问到bj之后,下次询问bi,只需在[b,b+j)即可
最坏情况n,n/2,n/4,复杂度O(2*n),即O(n)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e7+10;
typedef unsigned u;
int n,m;
u A,B,C,ans[N],a[N];
u x,y,z;
struct node
{
int id,rk;
}e[105];
bool operator<(node a,node b)
{
return a.rk<b.rk;
}
unsigned rng61()
{
unsigned t;
x = x ^ (x << 16);
x = x ^ (x >> 5);
x = x ^ (x << 1);
t = x;
x = y;
y = z;
z = (t ^ x) ^ y;
return z;
}
int main()
{
int ca=0;
while(~scanf("%d%d%u%u%u",&n,&m,&A,&B,&C))
{
x = A, y = B, z = C;
for(int i=0;i<n;++i)
a[i]=rng61();
for(int i=0;i<m;++i)
{
scanf("%d",&e[i].rk);
e[i].id=i;
}
sort(e,e+m);
e[m].rk=n;
for(int i=m-1;i>=0;--i)
{
//nth_element(start,rk,end) 使rk归位的操作
nth_element(a,a+e[i].rk,a+e[i+1].rk);
ans[e[i].id]=a[e[i].rk];
}
printf("Case #%d:",++ca);
for(int i=0;i<m;++i)
printf(" %u",ans[i]);
puts("");
}
return 0;
}
HDU 6041 I Curse Myself(仙人掌图求环+第k大和)
多组样例,每次给定一张n(n<=1e3)个点,m(m<=2n-3)的边的仙人掌,边权w<=1e6
求,其中V(k)为从小到大第k生成树的权值和,边权相同但树形不同视作两个
考虑仙人掌图,每个环独立,即每个环拆一条边,
最小生成树=权值总和-最大拆边和
把每个环扒下来排序构成一个集合,不妨共num个环,
问题转化成num个集合中,每个集合选取一个数,所组成的从大到小1到k的权值和
经典问题,如poj2442、uva11997(https://blog.csdn.net/Code92007/article/details/91906189)
但复杂度相比少log,证明看不大懂
空间要求比较严格,要求用滚动数组
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef unsigned un;
const int N=1e3+10;
const int M=2e3+10;
const int K=1e5+10;
struct edge
{
int v,nex,w;
}e[M*2];
struct node
{
int v,pos;
}f[N];
bool operator<(node a,node b)
{
return a.v<b.v;
}
int n,m,k,u,v,w;
int head[N],cnt;
int dfn[N],c[N],tot,num;
int sz[2],ans[2][K];
vector<int>cyc[N];
un res,sum;
priority_queue<node>q;
void add(int u,int v,int w)
{
e[++cnt]=edge{v,head[u],w};
head[u]=cnt;
}
void init()
{
memset(head,0,sizeof head);
memset(ans,0,sizeof ans);
memset(dfn,0,sizeof dfn);
for(int i=1;i<=num;++i)
cyc[i].clear();
res=sum=num=cnt=tot=0;
}
void dfs(int u,int fa)
{
dfn[u]=++tot;
for(int i=head[u];i;i=e[i].nex)
{
int v=e[i].v,w=e[i].w;
if(dfn[v]==-1||v==fa)continue;
c[tot]=w;
if(dfn[v])
{
num++;
for(int j=dfn[u];j>=dfn[v];j--)
cyc[num].push_back(c[j]);
}
else dfs(v,u);
}
tot--;
dfn[u]=-1;
}
void merge(vector<int> &x,int f,int *pre,int *now)
{
while(!q.empty())q.pop();
for(int i=0;i<x.size();++i)
q.push(node{x[i]+pre[1],1});//(v,nowid)
node t;
for(int i=1;i<=k;++i)
{
if(q.empty())break;
t=q.top();q.pop();
int v=t.v,pos=t.pos;
sz[f]=i;
now[i]=v;
if(pos+1<=sz[f^1])q.push(node{v-pre[pos]+pre[pos+1],pos+1});
}
}
int main()
{
int ca=0;
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1;i<=m;++i)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
sum+=w;
}
scanf("%d",&k);
dfs(1,-1);
sz[0]=1;//有一个0 作初值
for(int i=1;i<=num;++i)
{
sort(cyc[i].begin(),cyc[i].end(),greater<int>());
merge(cyc[i],i&1,ans[(i-1)&1],ans[i&1]);
}
//V(k)==0 if k-th spanning tree doesn't exist
for(un i=1;i<=sz[num&1];++i)
res+=i*(sum-(un)ans[num&1][i]);
printf("Case #%d: %u\n",++ca,res);
}
return 0;
}
HDU 6044 Limited Permutation(组合数学+dfs)
多组样例,n(n<=1e6)个数,要你设计一个1到n的排列,
使得第i个数在给定的[li,ri]中是最小值,求方案数
考虑有一个区间一定是[1,n],设这是第i个数,则第i个数一定填1
递归考虑[1,i-1]和[i+1,n]填什么,记左区间长度为l,右区间长度为r
则,先挑一些数给左边,剩下给右边
类似线段树维护的区间,[l,r]有两个子树[l,mid]和[mid+1,r],应按先序dfs这棵树
巧妙地发现,按左端点排增序,左端点相同时,再按右端点排降序,即可实现
注意无方案的几种情形及递归终点
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e6;
const int N=maxn+5;
int ca,n,now;
bool ok;
ll Finv[N],jc[N];
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;x=x*x%mod,n/=2)
if(n&1)res=res*x%mod;
return res;
}
void init()
{
jc[0]=Finv[0]=1;
for(int i=1;i<=maxn;++i)
{
jc[i]=jc[i-1]*i;
if(jc[i]>=mod)jc[i]%=mod;
}
Finv[maxn]=modpow(jc[maxn],mod-2,mod);
for(int i=maxn-1;i>=1;--i)
{
Finv[i]=Finv[i+1]*(i+1);
if(Finv[i]>=mod)Finv[i]%=mod;
}
}
ll C(ll n,ll m)
{
if(m<0||m>n)return 0;
return jc[n]*Finv[n-m]%mod*Finv[m]%mod;
}
struct node
{
int l,r,pos;
}e[N];
bool operator<(node a,node b)
{
if(a.l==b.l)return a.r>b.r;
return a.l<b.l;
}
ll dfs(int l,int r)
{
if(!ok)return 0;
if(l>r)return 1;
++now;
if(e[now].l!=l||e[now].r!=r||e[now].pos>r||e[now].pos<l)return ok=0;
ll res=1;
int pos=e[now].pos;
res=C(r-l,pos-l)*dfs(l,pos-1)%mod;
res=res*dfs(pos+1,r)%mod;
return res;
}
int main()
{
init();
while(~scanf("%d",&n))
{
ok=1;now=0;
for(int i=1;i<=n;++i)
scanf("%d",&e[i].l);
for(int i=1;i<=n;++i)
{
scanf("%d",&e[i].r);
e[i].pos=i;
}
sort(e+1,e+n+1);
printf("Case #%d: %lld\n",++ca,dfs(1,n));
}
return 0;
}