暑期集训2

90+90+20+4 204 rank12 嗯

题纲

T1:DP优化

T2:积性函数线性筛解决

T3:图论Dijstra

T4:树上计数DP+期望

T1:求两个数字串的最长上升公共子序列长度

(1)dp[i][j]:在A中匹配到i,在B中匹配到j,最长的公共上升子序列,以Bj结尾的长度。O(n^3)算法直接上,第一维循环A的位置,第二维循环B的位置,第三维如果A==B的话,找到A,B可以从B中某个位置继承过来的最优匹配

这个先把暴力写出来,看看转移的dp[i][k]就很好想,k就是满足Bk<Ai的最长答案,只从i-1转移,只来源于1--j-1(已经找过的历史状态),所以用Max记录一下就行。感性理解,就是当给Bj找到了Ai的匹配,因为i是第一维固定的,所以我要从<Ai的B中找到一个最优继承,A提到第一维是关键。O(n^2)



#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int mx=3100+100;
const int Inf=0x7fffffff;
ll f[mx],a[mx],b[mx],n;
void MYH(){
    scanf("%lld",&n);
    for(int i=1;i<=n;++i){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;++i){
        scanf("%lld",&b[i]);
    }
    for(int i=1;i<=n;++i){
        ll Max=0;
        for(int j=1;j<=n;++j){
            f[j]=f[j];
            if(a[i]>b[j])Max=max(Max,f[j]);
            if(a[i]==b[j])f[j]=Max+1;
        }
    }
    ll ans=0;
    for(int i=1;i<=n;++i){
        ans=max(ans,f[i]);
    }
    printf("%lld\n",ans);
}
int main(){
    freopen("lc.in","r",stdin);
    freopen("lc.out","w",stdout);
    MYH();
    return 0;
}
	for(int i=1;i<=n;++i){
		int Max=0;
		for(int j=1;j<=n;++j){
			f[i][j]=f[i][j-1];
			if(a[i]==b[j]){
				for(int k=1;k<=j-1;++k){
					if(b[k]<a[i])f[i][j]=max(f[i][j],f[i-1][k]+1);
				}
			}
		}
	}

可以用这个暴力理解一下

T2:定义f(x)=1(x=1);f(ab)=ab(a是质数);f(ab)=f(a)f(b)(gcd(a,b)=1),求\(\sum_{i=1}^{n}f[i]\)

一般积性函数都可以用线性筛。化简之后,对于任意数都可以拆成x=p1 ^ c1 * p2 ^ c2*........,所以f(x)=(p1^c1) * (p2c2)......(因为p1c1和p2^c2..互质,p1本身是质数)。在线性筛时(1)对于质数 :f[x]=x^1 (2) 对于合数:1.如果i%zhi[j]==0,那么就从x中筛掉所有zhi[j]因子次幂ct,f[x]=zhi[j] ^ ct * f[xx] (xx是筛完之后的,gcd()=1)

O(nlogn)

#include<iostream>
ll f[50000000+10];
int cnt,zhi[N],n;
bool prime[N];
void Prime()
{
    f[1]=1;ll ans=1;
    for(rint i=2;i<=n;++i){
    	if(!prime[i]){
    		zhi[++cnt]=i;f[i]=i^1;
		}
		for(rint j=1;j<=cnt;++j){
			ll me=zhi[j]*i;
			if(me>n)break;
			prime[me]=1;
			if(!(i%zhi[j])){
				ll tmp=me;int ct=0;
				while(1){
					if(!(tmp%zhi[j]))ct++;
					tmp/=zhi[j];
					if(tmp%zhi[j])break;
				}
				f[me]=(zhi[j]^ct)*f[tmp];
				break;
			}
			f[me]=f[i]*f[zhi[j]];
		}
		ans+=f[i];
	}
	chu("%lld",ans);
}
int main()
{
  freopen("func.in","r",stdin);
   freopen("func.out","w",stdout);  
    n=re();
    Prime();
	return 0;
}
/*
6
23333

171806766
10000000
49995001
*/

T3:给你一张有边权无向图,要求找到一个最小环,从1走又回到1,途中不能经过重复的边。保证没有重边自环(n<=1e4,m<=4e4)

最关键的思路就是考虑存在这样的方案,那么和1相连的边一定只能经过一次,所以我们就可以枚举这个环对于1的出边和入边,跑DIJ,因为没有负环,所以一定不会有重边。但是这样的复杂度是O(n^3 logn的)。我们发现依次枚举是多余的,我们只要保证在多次Dij的过程中,任意2个1连的点都会被作为待选起点和终点就行,可以二进制枚举,i位是1的就1-->x连边,是0的就x-->n+1连边,跑1-->n+1的最短路,这样假设最优决策是(i,j),因为i j一定有1位二进制不同,会被考虑进去,所以就是正确的。

真正压缩的、简化的东西其实是对于全面涵盖答案的方式,就比如,我给你1,2,3,4,5,让你算出方案数(1)每两个数不同都枚举一遍C(5,2)显然(2)要求任意2个数不同都在某种组合中出现,就2、3种情况,但是其实这道题要的只是“任意不同组合出现”

const int N=4e4+100;
struct node
{
  int to,nxt,w;
}e[N<<3];
int tot,head[10000+10],rem[10000+10],be1[10000+10],be_cnt,be_val[10000+10];
inline void Add(int u,int v,int w){
  e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;e[tot].w=w;
}
int n,m,T;
int dis[10000+10];bool vis[10000+10];
priority_queue<pair<int,int> >st;
inline void Dij()
{
  memset(dis,0x3f,sizeof(dis));
  memset(vis,0,sizeof(vis));
  st.push(make_pair(0,1));
  dis[1]=0;
  while(!st.empty())
  {
    int tp=st.top().second;vis[tp]=1;
    for(rint i=head[tp];i;i=e[i].nxt)
    {
      int to=e[i].to;
      if(dis[to]>dis[tp]+e[i].w)
      {
        dis[to]=dis[tp]+e[i].w;
        st.push(make_pair(-dis[to],to));
      }
    }
    while(!st.empty()&&(vis[st.top().second]))st.pop();
  }
}
int main()
{
	freopen("leave.in","r",stdin);
	freopen("leave.out","w",stdout);
  T=re();
  while(T--){
    n=re(),m=re();
    memset(head,0,sizeof(head));tot=0;be_cnt=0;
    _f(i,1,m){
      int fr=re(),to=re(),vl=re();
      if(fr==1||to==1){
        if(fr==1)fr=to;
        be1[++be_cnt]=fr;
        be_val[be_cnt]=vl;
      }
      else Add(fr,to,vl),Add(to,fr,vl);
    }
    _f(i,1,n)rem[i]=head[i];
    int ans=0x3f3f3f3f;
    for(rint i=0;(n>>i);i++)//按照几进制拆
    {
        int rel=tot;
        for(rint j=1;j<=be_cnt;j++)
        {
          int to=be1[j];
          if(to&(1<<i))Add(1,to,be_val[j]);
          else Add(to,n+1,be_val[j]);
        }
        Dij();
        ans=min(ans,dis[n+1]);
        tot=rel;
        _f(i,1,n)head[i]=rem[i];
    }
    if(ans<0x3f3f3f3f)
    chu("%d\n",ans);
    else chu("-1\n");
  }
	return 0;
}

T4:给你一棵树,任意一个节点等概率的选择一个儿子作为重儿子,我们把一棵树的复杂度称为一个树上从叶子到根节点经过的轻边数量和的最大值。求给定的树的复杂度期望。(n<=3000)

任意的需要DP的期望题一般都可以转化成\(\frac{本情况方案数}{总方案数}*贡献值\),贡献值好算,所以只需统计不同情况方案数。考虑本题的方案数统计:dp[i][j]:代表到i节点为止,复杂度<=j的方案数(小于等于好像又是省去了一维循环枚举,前缀和优化)。我们枚举重儿子是谁,枚举重儿子的贡献,dp[i][j]=\(\sum_{k\epsilon soni}^{}\)dp[k][j]\(*\prod\) dp[left_son][j-1].每到一个非叶子节点,枚举high_time,枚举重儿子,转移就行可以前缀后缀和优化一层枚举siz。注意(1)叶子节点因为只能贡献0,所以dp[leave][0]=1,然后因为1--n后面会用到也要初始化(2)对于只有一个儿子的,dp[only_one_son][0]=dp[the_son][0],因为多个儿子不能贡献0,但是1个儿子就有可能,而且当且仅当一条链的时候dp[rt][0]=1才能继承下去,否则就会忽略(3)最后前缀和不能直接*贡献,要减去

const int N=3010;const ll mod=1e9+7;
int n;
int root,mx_h[3010];
bool isfa[3010];
vector<int>son[3010];
ll dp[N][N],tot_ans,pre[3010],nxt[3010];
inline ll qpow(ll a,ll b)
{
    ll ans=1;
    while(b){
        if(b&1){
            ans=ans*a%mod;
        }
        b>>=1;
        a=a*a%mod;
    }
    return ans;
}
inline void dfs(int x)
{
    for(rint to:son[x]){
        dfs(to);
        mx_h[x]=max(mx_h[x],mx_h[to]+1);
    }
   // chu("回溯完了\n");
    int sara=son[x].size();
    if(!sara){
        _f(i,0,n)//我觉得n-1也行
        dp[x][i]=1;
        return;
    }
    if(sara==1)dp[x][0]=dp[son[x][0]][0];//但是,为什么是1。偶对,是方案数,如果是很多儿子,就一定没有可能是贡献0(一定有一个重儿子)
    for(rint k=1;k<=mx_h[x];++k)
    {
        pre[0]=nxt[sara+1]=1;
        _f(i,0,sara-1)pre[i+1]=pre[i]*dp[son[x][i]][k-1]%mod;
        f_(i,sara-1,0)
        {
            nxt[i+1]=nxt[i+2]*dp[son[x][i]][k-1]%mod;
        }
        _f(i,0,sara-1){
            dp[x][k]=(dp[x][k]+pre[i]*dp[son[x][i]][k]%mod*nxt[i+2]%mod)%mod;
        }
    }
    //对暴力的优化:预处理前缀后缀积
	// for(int k=1;k<=hi[root];++k)//O(High*size^2),可能会炸
	// {
	// 	for(int i=1;i<=cnt;++i)
	// 	{//哪个儿子是最大贡献
	// 		ll dev=dp[l[i]][k];
	// 		for(int ii=1;ii<=cnt;++ii)
	// 		{
	// 			if(ii==i)continue;//不一样的,就是剩下的
	// 			dev*=dp[l[ii]][k-1];
	// 		}
	// 		dp[root][k]+=dev;
	// 	}
	// }
    _f(i,mx_h[x]+1,n)dp[x][i]=dp[x][i-1];//继承,前缀和?但是意义本来就是
}
int main()
{
 freopen("tree.in","r",stdin);
  freopen("tree.out","w",stdout);
    n=re();tot_ans=1;
    _f(i,1,n){
        int k=re();
        if(k)tot_ans=tot_ans*k%mod;
        _f(j,1,k){
            int asdfgh=re();
            son[i].push_back(asdfgh);//儿子存一下
            isfa[asdfgh]=1;
        }
    }
    _f(i,1,n)
    if(!isfa[i])
    {
        root=i;break;
    }
   // chu("root:%d\n",root);
    dfs(root);
    ll ans=0;
    _f(i,1,n){
        ans=ans+(dp[root][i]-dp[root][i-1]+mod)*i%mod;
        ans%=mod;
    }
    chu("%lld",ans*qpow(tot_ans,mod-2)%mod);
	return 0;
}
posted on 2022-08-12 16:58  HZOI-曹蓉  阅读(29)  评论(0编辑  收藏  举报