暑期集训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:
暴力分: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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】