noip快要来了 要练练dp 难度也挺接近 还是挺好的
[Usaco2013 Nov]Pogo-Cow |
这一道题要下一段大于这一段 所以的话我们就要记录每一段的状态 F[i,j]=F[j,k]+A[i] (j-i<=k-j, i<j<k)
然后我们可以优化一下 k是可以二分处理的 F[i,j]=F[j,k-n]+A[i] 然后就是树状数组优化一下 写到一半才发现可以写优先队列..
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cstdlib> #define Maxn 1010 using namespace std; bool Cmp(const pair<int,int> &x,const pair<int,int> &y){return x.first<y.first;} pair<int,int>pr[Maxn]; int tr[Maxn][Maxn]; int low_bit(int x){return x&(-x);} int N; void Add(int k,int x,int c){while(x<=N) {tr[k][x]=max(tr[k][x],c); x+=low_bit(x);}} int Find(int k,int x){int maxx=0; while(x>=1){maxx=max(maxx,tr[k][x]); x-=low_bit(x);} return maxx;} int twopart1(int x,int c) { int ret=-1; int L=x; int R=N; while(L<=R) { int mid=(L+R)>>1; if(pr[mid].first-pr[x].first>=c) R=mid-1,ret=mid; else L=mid+1; } return ret; } int twopart2(int x,int c) { int ret=-1; int L=1; int R=x; while(L<=R) { int mid=(L+R)>>1; if(pr[x].first-pr[mid].first>=c) L=mid+1,ret=mid; else R=mid-1; } return ret; } int main() { scanf("%d",&N); for(int i=1;i<=N;i++) scanf("%d%d",&pr[i].first,&pr[i].second); sort(pr+1,pr+N+1,Cmp); memset(tr,0,sizeof(tr)); int Maxx=0; for(int i=1;i<=N;i++) for(int j=i+1;j<=N;j++) Add(i,N-j+1,pr[i].second+pr[j].second),Maxx=max(Maxx,Find(i,N-j+1)); for(int i=N;i>=1;i--) { for(int j=i+1;j<=N;j++) { int k=twopart1(j,pr[j].first-pr[i].first); if(k!=-1) Add(i,N-j+1,Find(j,N-k+1)+pr[i].second); Maxx=max(Maxx,Find(i,N-j+1)); } } memset(tr,0,sizeof(tr)); for(int i=1;i<=N;i++) for(int j=1;j<i;j++) Add(i,j,pr[i].second+pr[j].second),Maxx=max(Maxx,Find(i,j)); for(int i=1;i<=N;i++) { for(int j=1;j<i;j++) { int k=twopart2(j,pr[i].first-pr[j].first); if(k!=-1) Add(i,j,Find(j,k)+pr[i].second); Maxx=max(Maxx,Find(i,j)); } } return printf("%d ",Maxx),0; } /* 6 5 6 1 1 10 5 7 6 4 8 8 10 */
hdu5291 Candy Distribution
多校的题哦很激动 很少碰到
然后yy了一下 一下子想到了 F[i][j]表示选i份 A与B差为j 方案数大小
然后转移是 F[i][j]=F[i-1][k]*floor((A[i]-|j-k|)/2)+1
然后好像是N^4的 好像不行 怎么办好呢
看题解咯
其实每一层都对下一层有关系 我们不妨写成F[i][j+k或j-k]+=F[i-1][j]*floor((A[i]-k)/2)+1
那么的话我们会发现这个有个下去整 也就是和奇数和偶数有关 然后每次都是递增或者递减的 就是乘于F[i-1][j]的次数是递增或者是递减1的
然后怎么搞呢 我们对于每一个F[i]搞一下 去更新F[i+1] 然后拿一个指针跳下标每次加2 用一个差不多lazy的数组打每个位置的值
我们想想 每一次F[i][j]转移的时候 只要在开头前面打一个F[i][j] 然后途中递增的时候每一次都加上之前的lazy lazy就相当于每一次增加的量
然后递减的时候标记减两次F[i][j] 这样的话lazy变成-F[i][j]所以每次都会减掉F[i][j] 然后最后的时候再加一个 使全部都不增不减 免得影响后面
我们途中可以优化一下 就是每一次最多能到达的点就是加减前面所有A[i]的总和 这样速度快一倍
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cstdlib> #include<cmath> #define Maxn 210 using namespace std; const int Mod=1e9+7; const int Maxm=80010; int F[2][80010]; int B[80010]; int T,N; int A[Maxn]; int cur; int main() { scanf("%d",&T); while(T--) { scanf("%d",&N); for(int i=1;i<=N;i++) scanf("%d",&A[i]); int cur=0; memset(F,0,sizeof(F)); F[cur][40005]=1; int tot=0; for(int i=0;i<N;i++) { tot+=A[i+1]; for(int j=40005-tot-2;j<=40005+tot+2;j++) B[j]=0,F[cur^1][j]=0; for(int j=40005-tot;j<=40005+tot;j++) { if(F[cur][j]) { B[j-A[i+1]-1]=(B[j-A[i+1]-1]+F[cur][j])%Mod; B[j-A[i+1]-2]=(B[j-A[i+1]-2]+F[cur][j])%Mod; B[j-1]=(B[j-1]-F[cur][j])%Mod; B[j+1]=(B[j+1]-F[cur][j])%Mod; B[j]=(((B[j]-F[cur][j])%Mod)-F[cur][j])%Mod; B[j+A[i+1]+1]=(B[j+A[i+1]+1]+F[cur][j])%Mod; B[j+A[i+1]+2]=(B[j+A[i+1]+2]+F[cur][j])%Mod; } } int last=0,ans=0; for(int j=40005-tot-2;j<=40005+tot+2;j+=2) { ans=(ans+last)%Mod; F[cur^1][j]=(ans+Mod)%Mod; last=(last+B[j])%Mod; } last=0; ans=0; for(int j=40005-tot-1;j<=40005+tot+2;j+=2) { ans=(ans+last)%Mod; F[cur^1][j]=(ans+Mod)%Mod; last=(last+B[j])%Mod; }cur^=1; } printf("%d ",F[cur][40005]); } return 0; } /* 2 1 2 2 1 2 */
CH Round #54 - Streaming #5 (NOIP模拟赛Day1) 高维网络
这一道题非常noip提高组第三题 出得非常好 我看了看范围可以尽能力水70应该是没什么问题的
这题因为P小 所以从P下手 然后就是上图了
我想补充的是P=0的做法 就是一个组合数学 你想想在D维空间下 你从A走到B 是要走sigma(Ai)步的 那么我把每一维都变成一个序列1 2 3 ... A[i]
那么就是变成了我在sigma(Ai)步中 让这些序列组合起来 然后每一个序列的数 一定在一些位置上抽出来后也是从小到大的
那么就等于这样的组合数 很抽象吧 等于从空间上走一维 就在一个格子上填一个数
怎么算呢 我们以三个序列个数为例 x,y,z
=C(x,x+y+z)*C(y,y+z) 什么意思呢 就是我现在x+y+z个格子上填x个 就把x给搞完了 然后在其他剩余填y-z的格子上再填y 然后相乘(这不用解释吧 x能填的方案和其他格子的方案相乘)
然后化简得p=0的式子
这道题还有一个妙的地方就是把所有不合法的路径等于以某个点为起始点 第一个不合法的点 到终点的不合法路径的总和 这样的话保证不会重复(我觉得非常妙可能是我太弱了)
exgcd求逆元好像会快很多
#include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #include<algorithm> #define Maxn 510 using namespace std; typedef long long LL; const LL Mod=1e9+7; struct node{LL A[Maxn];}Q[Maxn]; LL D,P; bool Cmp(const node &x,const node &y) { for(LL i=1;i<=D;i++) if(x.A[i]!=y.A[i]) return x.A[i]<y.A[i]; return 0; } LL DP[Maxn]; bool check(LL x,LL y){for(LL i=1;i<=D;i++) if(Q[x].A[i]>Q[y].A[i]) return 0; return 1;} LL ny[100010]; LL pre[100010]; void exgcd(LL a,LL b,LL &x,LL &y) { if(b==0){x=1; y=0; return ;} else{LL tx,ty; exgcd(b,a%b,tx,ty); x=ty; y=tx-(a/b)*ty;} } LL Sum[10000010]; LL part(LL x,LL y) { LL s=0; LL p=1; for(LL i=1;i<=D;i++) { s+=Q[y].A[i]-Q[x].A[i]; p=(p*(pre[Q[y].A[i]-Q[x].A[i]]))%Mod; } return ((Sum[s]*p)%Mod+Mod)%Mod; } int main() { for(LL i=1;i<=100000;i++){LL x,y; exgcd(i,Mod,x,y); ny[i]=x;} pre[0]=1; for(LL i=1;i<=100000;i++){pre[i]=(pre[i-1]*ny[i])%Mod;} Sum[0]=1; for(LL i=1;i<=10000000;i++) Sum[i]=(Sum[i-1]*i)%Mod; scanf("%lld%lld",&D,&P); for(LL j=1;j<=D;j++) scanf("%lld",&Q[P+1].A[j]); for(LL i=1;i<=P;i++) for(LL j=1;j<=D;j++) scanf("%lld",&Q[i].A[j]); for(LL j=1;j<=D;j++) Q[0].A[j]=0; P++; sort(Q,Q+P+1,Cmp); for(LL i=0;i<=P;i++) { DP[i]=part(0,i); for(LL j=1;j<i;j++) if(check(j,i)) DP[i]=(DP[i]-(DP[j]*part(j,i))%Mod+Mod)%Mod; } return printf("%lld ",DP[P]),0; } /* 2 1 2 1 1 0 A[i]<=100000 D<=100 P<=500 */
这一道题应该放在提高组的第二题左右吧
一开始想暴力dp水分 发现错了 F[i][j]大概表示选了i个人剩下j的花费的方案 发现转移很慢 而且还会有错
然后换思路 我们发现如果Ai>Aj i>j 的话 这个Ai是没意义的
那么我们不妨从小到大排 然后把每个数插在最前面或者最后面算一下
但是如果定义F[ij[j]表示选第i个了剩下j的方案数的话 好像转移不了
那我们想一想 可不可以写成F[i][j]表示选第i个 给j的数量给它去mod 最大的数是什么? 这样好像可以转移了 j%A[i]->j
然后的话就用G[i][j]表示选最大的数的方案数咯 记住 如果放在后面的话有(i-1)种放法,放在之前每一个数的后面 因为都没关系
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cstdlib> #define Maxn 1010 using namespace std; typedef long long LL; const LL Mod=7*17*(1<<23)+1; LL N,X; LL A[Maxn]; LL F[Maxn][5010]; LL G[Maxn][5010]; int main() { scanf("%lld%lld",&N,&X); for(LL i=1;i<=N;i++) scanf("%lld",&A[i]); sort(A+1,A+N+1); memset(F,0,sizeof(F)); memset(G,0,sizeof(G)); for(LL i=0;i<=X;i++) F[1][i]=i%A[1],G[1][i]=1; for(LL i=2;i<=N;i++) { for(LL j=0;j<=X;j++) { if(F[i-1][j%A[i]]>F[i-1][j]) F[i][j]=F[i-1][j%A[i]]; else F[i][j]=F[i-1][j]; if(F[i][j]==F[i-1][j]) G[i][j]=(G[i-1][j]*(i-1)+G[i][j])%Mod; if(F[i][j]==F[i-1][j%A[i]]) G[i][j]=(G[i-1][j%A[i]]+G[i][j])%Mod; } } return printf("%lld %lld ",F[N][X],G[N][X]),0; } /* 2 15 7 10 */