随笔 - 188  文章 - 0  评论 - 59  阅读 - 7707

暑期集训6

rank 19 mark 70

题纲:T1:接力比赛(DP优化(随机化+上界递增优化));T2:树上竞技:计数类DP;T3:思维(暴力找max距离的优化:从最大距离的一遍反着找最近的距离点对);T4:记忆碎片:计数类DP

由于进度问题,我不想写博客了,具体的题目可以看:https://tg.hszxoj.com/contest/509

题解:

T1:

随机化,边界取不会T的最大
struct node
{
    ll w, v;
}p[maxn], q[maxn];

int main()
{
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);
    
    n = read(); m = read();
    for(int i=1; i<=n; i++)
    {
        p[i].w = read(); p[i].v = read();
        s1 += p[i].w;
    }
    for(int i=1; i<=m; i++)
    {
        q[i].w = read(); q[i].v = read();
        s2 += q[i].w;
    }
    W1 = min(s1, s2); W2 = max(s1, s2);
    for(int i=1; i<=W2; i++)
    {
        dp1[i] = dp2[i] = -1e12;
    }
    //f1[0] = 1;
    W = 3.5e5;
    for(int i=1; i<=n; i++)
    {
        for(int j=W; j>=p[i].w; j--)
        {
            //printf("%d %lld\n", j, j-p[i].w);
            //printf("hefa : %d %d\n", f1[j], f1[j-p[i].w]);
            //f1[j] = f1[j] | f1[j-p[i].w];
            //if(f1[j-p[i].w])
            //{
                dp1[j] = max(dp1[j], dp1[j-p[i].w]+p[i].v);
                //printf("dp1[%d] = %lld\n", j, dp1[j]);
            //}
        }
    }
    //f2[0] = 1;
    for(int i=1; i<=m; i++)
    {
        for(int j=W; j>=q[i].w; j--)
        {
            //printf("%d %lld\n", j, j-q[i].w);
            //printf("hefa: %d %d\n", f2[j], f2[j-q[i].w]);
            //f2[j] = f2[j] | f2[j-q[i].w];
            //if(f2[j-q[i].w])
            //{
                dp2[j] = max(dp2[j], dp2[j-q[i].w]+q[i].v);
                //printf("dp2[%d] = %lld\n", j, dp2[j]);
            //}
        }
    }
    for(int i=1; i<=W1; i++)
    {
        //if(f1[i] && f2[i])
        //{
            ans = max(ans, dp1[i]+dp2[i]);
            //printf("dp1[%d] = %lld dp2 = %lld\n", i, dp1[i], dp2[i]);
        //}
    }
    printf("%lld\n", ans);
  
    return 0;
}


边界优化,sort一下效果更好
int n, m;
ll s1, s2, W, ans, dp1[N], dp2[N], W1, W2;
bool f1[N], f2[N];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node
{
    ll w, v;
    bool operator < (const node &T) const 
    {
        if(w == T.w) return v < T.v;
        return w < T.w;
    }
}p[maxn], q[maxn];

int main()
{
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);
    
    n = read(); m = read();
    for(int i=1; i<=n; i++)
    {
        p[i].w = read(); p[i].v = read();
        //s1 += p[i].w;
    }
    for(int i=1; i<=m; i++)
    {
        q[i].w = read(); q[i].v = read();
        //s2 += q[i].w;
    }
    sort(p+1, p+1+n);
    sort(q+1, q+1+m);
    memset(dp1, -64, sizeof(dp1));
    memset(dp2, -64, sizeof(dp2));
    dp1[0] = dp2[0] = 0;
    //W1 = min(s1, s2); W2 = max(s1, s2);
    //for(int i=1; i<=W2; i++)
    //{
        //dp1[i] = dp2[i] = -1e12;
    //}
    //f1[0] = 1;
    //W = 3.5e5;
    for(int i=1; i<=n; i++)
    {
        s1 += p[i].w;
        for(int j=s1; j>=p[i].w; j--)
        {
            //printf("%d %lld\n", j, j-p[i].w);
            //printf("hefa : %d %d\n", f1[j], f1[j-p[i].w]);
            //f1[j] = f1[j] | f1[j-p[i].w];
            //if(f1[j-p[i].w])
            //{
                dp1[j] = max(dp1[j], dp1[j-p[i].w]+p[i].v);
                //printf("dp1[%d] = %lld\n", j, dp1[j]);
            //}
        }
    }
    //f2[0] = 1;
    for(int i=1; i<=m; i++)
    {
        s2 += q[i].w;
        for(int j=s2; j>=q[i].w; j--)
        {
            //printf("%d %lld\n", j, j-q[i].w);
            //printf("hefa: %d %d\n", f2[j], f2[j-q[i].w]);
            //f2[j] = f2[j] | f2[j-q[i].w];
            //if(f2[j-q[i].w])
            //{
                dp2[j] = max(dp2[j], dp2[j-q[i].w]+q[i].v);
                //printf("dp2[%d] = %lld\n", j, dp2[j]);
            //}
        }
    }
    W = min(s1, s2);
    for(int i=1; i<=W; i++)
    {
        //if(f1[i] && f2[i])
        //{
            ans = max(ans, dp1[i]+dp2[i]);
            //printf("dp1[%d] = %lld dp2 = %lld\n", i, dp1[i], dp2[i]);
        //}
    }
    printf("%lld\n", ans);
  
    return 0;
}


T2:
image

暴力分:https://www.cnblogs.com/Catherine2006/p/16600202.html(from Catherine_leah)
T3:
细节(1)double开够(2)因为表针是一个环,所以pos=0-->=n,pos=n+1-->=0,if(h>=12)h-=12

int n;
double sz[50100],fz[50100];
inline double get_hour(double x,double y,double z){return x*30.0+y*0.5+z*0.5/60.0;}
inline double get_minu(double x,double y,double z){return y*6.0+z*0.1;}
inline double Dis(double x,double y)
{
	if(x<y)swap(x,y);
	return ((x-y)>180.0)?(360.0-x+y):(x-y);
}
char sre[10];
int main()
{
	freopen("unreal.in","r",stdin);
	freopen("unreal.out","w",stdout);
	n=re();
	_f(i,1,n)
	{
		double h=re(),m=re(),s=re();
		if(h>=12)h-=12;
		sz[i]=get_hour(h,m,s);
		fz[i]=get_minu(h,m,s);
	}
	double myasn=1000000000.00;
	sort(fz+1,fz+1+n);
	sort(sz+1,sz+1+n);
	for(double i=0;i<12;i+=1)
	for(double j=0;j<60;j+=1)
	for(double k=0;k<60;k+=0.01)
	{
		double t1=get_hour(i,j,k);
		double t2=get_minu(i,j,k);
		double ft1=(t1<180.0)?(t1+180.0):(t1-180.0);
		double ft2=(t2<180.0)?(t2+180.0):(t2-180.0);
		int p1=upper_bound(sz+1,sz+1+n,ft1)-sz;
		int p2=upper_bound(fz+1,fz+1+n,ft2)-fz;
		int pp1=p1-1;
		int pp2=p2-1;
		if(p1==n+1)p1=1;
		if(p2==n+1)p2=1;//就是随便算一个?
		if(pp1==0)pp1=n;
		if(pp2==0)pp2=n;
		//p1 and p1-1-->算和t的距离差
		//p2 and p2-1
		double ans1=Dis(t1,sz[p1]);
		ans1=max(ans1,Dis(t1,sz[pp1]));
		double ans2=Dis(t2,fz[p2]);
		ans2=max(ans2,Dis(t2,fz[pp2]));
		if(ans1>ans2)myasn=min(myasn,ans1);
		else myasn=min(myasn,ans2);
	}
	chu("%lf",myasn);
	return 0;
}

T4:
part1:暴力:
还是逆向思维,反客为主,你让我求有要求的构造方案数,我就枚举所有状态看合不合法。(之前那个大佬说谎的也是,枚举每个人说的话,看和真话矛不矛盾)就是构造一个完全图,然后跑最小生成树,看和给出来的符不符合。
怎么构造?就是你生成一个n*(n-1)/2的排列,表示每条边的权值,按照for(i)for(j:i+1,n)的顺序给边排列赋值,就是构造出来的包含所有情况的完全图,然后kruscal跑最小生成树,就是从小到大边权,比较一下就行,对了就ans++。
part2:正解
假设dp[i][j]:表示已经分配完前i条树边,当前的n个节点的拆分状态是j的方案个数。重点在于对当前图所处于状态的表示,n<=40,所以如果把完全图拆分成若干个大小不一的块,可以证明有<=40000种拆分数的方案,所以我提前处理出P,来对应图当前状态到底含有多少个块、每个大小的块有几个节点,合并当前的某2个块(加入一条树边),可以到达哪些状态(用一个新建的“DP”图)方便转移。
这样,我们枚举树边,枚举当前可以转移的“分拆数”状态。如果当前不是第一条树边,那么两条树边之间的长度的边就一定是非树边才合法,我们把它插入到各个联通块里,如果非树边连续,那么对于剩余的边,第一个非有lef种填法,第二个有lef-1种....,用下降幂优化枚举,方案个数增加到原i-1的树边里(不改变连通性);如果当前要加入一条树边,一定会联通2个块,枚举分拆方案,枚举合并哪些数到达的状态(预处理的图),转移,方案数就是(1)如果siz1=siz2,从其中任选2个子联通块,选哪个都一样,所以是一个C(siz,2)(2)如果siz1!=siz2,就是从2个不同的联通块选,独立,直接乘法原理。
对于这种抽象的状态转移,甚至连点的编号都忽略,可以理解成我初始就默认对图进行了点的有序编号,对于每种分拆方案,都是若干种有编号的点连边方案的集合。





#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<deque>
#include<queue>
#include<map>
#include<set>
#include<bitset>
using namespace std;
#define rint register int
#define _f(i,a,b) for(rint i=a;i<=b;++i)
#define f_(i,a,b) for(rint i=a;i>=b;--i)
#define chu printf
#define ll long long
#define INF 2147483647
#define ull unsigned long long
#define int ll
inline ll re()
{
	ll h=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*h;
}
const int N=40+10;const ull base=131;const ll mod=1e9+7;
int n,a[N];
struct State
{
    bitset<43>p;
    int cnt[N];//表示这么大的联通块有几个
    inline void add(int x,int y){if(y&&(!p[x]))p[x]=1;cnt[x]+=y;}
    inline void del(int x,int y){cnt[x]-=y;if(!cnt[x])p[x]=0;}
    inline ull hash()
    {
        ull bas=0;
        _f(i,1,n)bas=bas*base+cnt[i];
        return bas;
    }
}P[50000];
int pcnt;
struct Edge
{
    int fr,to,nxt,x,y;
}e[4000*4000];//所以这个边数怎么算?
int tot,head[50000];
map<ull,int>mp;//用hash表示的状态映射
inline void Add(int x,int y,int ax,int ay)
{
    //chu("add%d-->%d\n",x,y);
    e[++tot].nxt=head[x];head[x]=tot;e[tot].x=ax,e[tot].y=ay;
      e[tot].fr=x;e[tot].to=y;
}
queue<int>sta;
inline void Pre()
{
    ++pcnt;
    sta.push(1);P[1].p[1]=1;P[1].cnt[1]=n;
    mp[P[1].hash()]=1;//标记!
    while(!sta.empty())
    {
        int u=sta.front();sta.pop();//清空!
        State A=P[u];
        int pr=P[u].p._Find_first();//第一个有数的位数
        while(P[u].cnt[pr]&&pr<=n)//边界?可能是会炸?
        {
            if(P[u].cnt[pr]>=2)
            {
                A.del(pr,2);A.add(pr+pr,1);ull stu=A.hash();
                if(mp.find(stu)==mp.end()){mp[stu]=pcnt+1,P[++pcnt]=A;sta.push(pcnt);}
                Add(u,mp[A.hash()],pr,pr);
                A.add(pr,2);A.del(pr+pr,1);
            }
            int sub=P[u].p._Find_next(pr);
            while(P[u].cnt[sub]&&pr<=n)//只要是有数吧
            {
                A.del(pr,1);A.del(sub,1);A.add(pr+sub,1);ull stu=A.hash();
                if(mp.find(stu)==mp.end()){mp[stu]=pcnt+1,P[++pcnt]=A;sta.push(pcnt);}
                Add(u,mp[A.hash()],pr,sub);
                //可能没有新加pcnt要自己找
                A.add(pr,1);A.add(sub,1);A.del(pr+sub,1);
                sub=P[u].p._Find_next(sub);
            }
            pr=P[u].p._Find_next(pr);
        }
    }
}
inline ll qpow(ll a,ll b)
{
    ll bas=1;
    while(b)
    {
        if(b&1){bas=bas*a%mod;}
        b>>=1;a=a*a%mod;
    }
    return bas;
}
int fac[3000],inv[3000],sinv[3000];
inline void Inv()
{
    fac[0]=fac[1]=inv[0]=inv[1]=sinv[0]=sinv[1]=1;
    _f(i,2,n*(n-1)/2)
    {
        fac[i]=fac[i-1]*i%mod;
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        sinv[i]=sinv[i-1]*inv[i]%mod;
    }
}
inline int Down(int a,int b)
{
    if(a<b)return 0;
    return fac[a]*sinv[a-b]%mod;
}
int dp[N][50000];
signed main()
{
	freopen("tree.in","r",stdin);//文件输入输出!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
	freopen("tree.out","w",stdout);
    n=re();
    _f(i,1,n-1)a[i]=re();
    Pre();Inv();int xs2=qpow(2,mod-2);
    dp[0][1]=1;
   // _f(i,1,pcnt)
  //  _f(j,1,n)
  //  chu("P:编号%lld 大小%lld个数  %lld\n",i,j,P[i].cnt[j]);
   // chu("xs:%lld\n",xs2);
    _f(i,1,n-1)
    {
        if(i!=1)
        {
            _f(j,1,pcnt)//枚举所有分拆状态
            if(dp[i-1][j])
            {
                int sum=0;int pos=P[j].p._Find_first();
                while(pos<=n&&P[j].cnt[pos])
                {
                    sum=(sum+1LL*(P[j].cnt[pos])*pos*(pos-1)/2)%mod;//记录边数
                    //pos是个数点的个数
                    pos=P[j].p._Find_next(pos);
                }
                sum-=a[i-1];//这些边已经算过了
                dp[i-1][j]=(dp[i-1][j]*Down(sum,a[i]-a[i-1]-1))%mod;
                //chu("(last)dp[%d][%d]:%d\n",i-1,j,dp[i-1][j]);
                //是乘法,因为树边*非树边,2个独立事件共同组成
            }
        }
        _f(j,1,pcnt)
        if(dp[i-1][j])
        {
            int sum=0;
            for(rint tp=head[j];tp;tp=e[tp].nxt)
            {
              // printf("fr:%d  to:%d\n",j,e[tp].to);
                sum=0;
                int to=e[tp].to;int cnta=e[tp].x,cntb=e[tp].y;
            if(cnta==cntb)//来自同一个图
            {
 sum=(cnta*cnta%mod*P[j].cnt[cnta]%mod*(P[j].cnt[cnta]-1)%mod*xs2%mod)%mod;
             }
            else 
            {
                //chu("not same %d %d %d %d\n",cnta,cntb,P[j].cnt[cnta],P[j].cnt[cntb]);
sum=(cnta*cntb%mod*P[j].cnt[cnta]%mod*P[j].cnt[cntb]%mod)%mod;
            }
           // chu("cnt%d  %d,sum:%d\n",cnta,cntb,sum);
            dp[i][to]=(dp[i][to]+sum*dp[i-1][j])%mod;
            //chu("dp[%d][%d]:%d\n",i,to,dp[i][to]);
            }
        }
     //   printf("rond%d\n",i);
	//	for(int ii=1;ii<=n-1;++ii)for(int j=1;j<=3;++j)printf("dp[%d][%d]:%d\n",ii,j,dp[ii][j]);
    }
    int df=0;
    _f(i,1,pcnt)
    if(P[i].cnt[n]){df=i;break;}
    int lef=n*(n-1)/2-a[n-1];
    //chu("lef:")
   // chu("df:%d\n",df);
    _f(i,1,lef)dp[n-1][df]=dp[n-1][df]*i%mod;
    chu("%lld",dp[n-1][df]);
	return 0;
}


posted on   HZOI-曹蓉  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示