jzyz 题库 题目选做
题库中也有很多我想不出来的模拟赛的题目。做还是必要的。做自己的题目 时间很紧 想想自己的文化课 我又没有那么强 我必须得刷.
LINK:水题一道
发现是一道计数题 计数题拿高分的才是王者,但是 计数题不取模就是在耍流氓了...好久没写高精了 顺便写写。
此题 我们发现 不管怎么搞每个教室都需要两个人我们不妨 先让m-=2*n; 然后就是剩下的m个人的分配问题了 感觉是组合数其实并不尽然。因为显然不对...
这样 对于一个人来说他有n种可能 那么m个人呢?m^n 但是显然有重复如第一个人选第二个 第二个人选第一个 和第一个人选第一个第二个人选第二个这样是等效的...
考虑排列数对我们答案的影响,不管排列的话其实除以一个m!即可。然后成功想错 这样的做法是错误的。因为这有重复除以m!根本什么也不是。。。没有什么排列数...
正解是隔板法 再次隔板 设当前人数为 s=m-2*n 那么其实有将s个人分到n个房间中 且每个房间总和为s 也就是说 x1+x2+x3+x4+...=s 这显然是隔板法了...
注意到x可能为0 所以再补板n-1 空隙 还是插板n-1个即可。m为500 用头想都是高精。C(s+n-1,n-1) 。。。想10min都没怎么发现这个模型藏得有点深。
约分是必要的 分解质因数再约分 就变成高精乘单精了。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<ctime> #include<cstring> #include<string> #include<ctime> #include<cctype> #include<cstdio> #include<utility> #include<queue> #include<stack> #include<deque> #include<map> #include<set> #include<bitset> #include<vector> #include<algorithm> #include<cstdlib> #define ll long long using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=1010; int s0,s1,s2; int p0[MAXN],p1[MAXN],p2[MAXN]; int b[MAXN],top; int n,m,s,c; inline void insert(int x,int *a) { int s=x; for(int i=2;i*i<=x;++i) while(s%i==0) { s=s/i; ++a[i]; } if(s>1)++a[s]; } inline void mul(int x) { int res=0; for(int i=1;i<=top;++i) { b[i]=b[i]*x; b[i]+=res; res=b[i]/10; b[i]=b[i]%10; } while(res) { b[++top]=res; res=b[top]/10; b[top]=b[top]%10; } } int main() { //freopen("1.in","r",stdin); n=read();m=read(); if(m<2*n){puts("0");return 0;} s=m-2*n+n-1;c=n-1;//求 C(s,c); for(int i=2;i<=s;++i)insert(i,p0); for(int i=2;i<=c;++i)insert(i,p1); for(int i=2;i<=s-c;++i)insert(i,p2); b[++top]=1; for(int i=2;i<=s;++i) { p0[i]=p0[i]-p1[i]-p2[i]; while(p0[i]) { --p0[i]; mul(i); } } for(int i=top;i>=1;--i)printf("%d",b[i]); return 0; }
LINK:免费馅饼
咕了 这么长时间了 也该写写了... 看起来很显然是dp 但是还要输出方案...(那也很水
第一次写状态转移写错了 ,惊了 瞎写了一个状态转移..感觉不太对..当然f[i][j]表示第i秒到了第j个位置的最大价值 f[i][j]=max(f[i-1][j-1],f[i-1][j-2]...)然后 馅饼的话开vector记录下来,到达这个位置就累加上价值即可。
有一个坑点是 对于一个馅饼 下降高度为 h-1 题目中说道 恰好在某一秒末和人相遇才行也就是 h-1%v!=0的都不能要。还有一个坑点是 方案要从最后一个馅饼开始找而不是最后一秒开始...
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define R register #define db double #define max(x,y) ((x)>(y)?(x):(y)) using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=110,maxn=2010; int n,m,cnt,maxx,ans; int st[MAXN],top; struct wy { int s,x,v,p; }t[maxn]; int f[maxn][MAXN];//f[i][j]表示第i秒在x这个位置所能获得的最大分数 int w[maxn][MAXN]; vector<int>g[maxn]; inline int abs(int x){return x>0?x:-x;} inline void get_path(int x,int y) { if(!x)return; st[++top]=-(w[x][y]-y); get_path(x-1,w[x][y]); } int main() { //freopen("1.in","r",stdin); n=read();m=read(); int s,x,v,p,h=m;--h; while(scanf("%d",&s)==1) { x=read();v=read();p=read(); if(h%v!=0)continue; t[++cnt]=(wy){s,x,v,p}; int st=s+h/v; g[st].push_back(cnt); maxx=max(maxx,st); } memset(f,0xcf,sizeof(f)); f[0][(n>>1)+1]=0; for(int i=1;i<=maxx;++i) { for(int j=1;j<=n;++j) { for(int k=max(1,j-2);k<=min(n,j+2);++k) { if(f[i][j]<f[i-1][k]) { f[i][j]=f[i-1][k]; w[i][j]=k; } } } for(int k=0;k<(int)g[i].size();++k) { int r=g[i][k]; f[i][t[r].x]+=t[r].p; } } for(int i=1;i<=n;++i)ans=max(ans,f[maxx][i]); printf("%d\n",ans); for(int i=1;i<=maxx;++i) { for(int j=1;j<=n;++j) { if(f[i][j]==ans) { get_path(i,j); for(int k=top;k>=1;--k)printf("%d\n",st[k]); return 0; } } } return 0; }
update :其实这个也可以不用vector存我们利用排序后时间从大到小的顺序便利整个数组即可 省掉空间和时间...
LINK:字符合并
早就想写这道题了 受不了了 这道题 是水题..我坚信。(然后想了30min 被打脸...
还是 字符串之间进行合并 值得一提的是这次只有01串 每k个字符可以合成一个字符 存在一个命题是 如果(n-1)%(k-1)==0的话那么则有 最后合并剩下一个字符 (我也不知道是怎么证明的..
首先还是设计状态吧 反正是区间dp 转移着实有点困难 但在我坚持不懈的努力之下 还是成功的进行转移了 但是不知道对不对 感觉正确性还是有的..
f[i][j][k]表示 由i到j状态为k的最大 代价 那么转移就是区间dp 然后 枚举断点 唯一不同的是还需要枚举左边状态s1 右边状态s2 然后merge一下即可 merge有点难写 但是仔细思考还是可以O(1)的...
但是这样的复杂度是n^3*2^k*2^k=n^3*4^k; 显然过不了1e12 。
这里先放暴力(我不知道对不对 不想交):发现暴力是瞎写的 不放了
当然 我觉得这个merge写的还算很精辟/cy.(精辟锤子 写错了。
考虑优化一个比较显然的地方是每次我们枚举状态的时候其实不必要枚举到1<<(k-1) 因为对于一个区间来说其固定的状态大小是存在的 (n-1)%(k-1)+1其实就是这个东西了...每8次一循环每个循环的复杂度是2^1-1+2^2-1+...2^7-1加起来是2^8那么这样其实复杂度骤降8倍..
还有一个不太明显的优化是 进行状态合并的时候 如果没有合成 也就是不到k个字符的合成 左边合成和右边合成其实都是等效的 所以我们可以省掉右边的合并钦定左边进行合并即可。
那么就是 我们只在乎右边合成了1或者0 至于更多的可以不管交给左边去合成即可。这样复杂度降为n^3*2^k/8 可以calc一下 发现可以过了.
这样对于左边的状态我们还是可以利用(n-1)%(k-1)这个东西来剪枝。
那么这道题也就做完了 我看了一眼书才发现的解法 我也没想到第二个优化 可能脑子有点乱..以后dp的时候要清醒 别看书或看题解..这点小优化还是可以想出来的我觉得。
一直wa 发现是合并的时候 没有判断准确合并的大小和长度 不是随便合并的 0000和1可以合并但是状态得自己看 有的时候0 可能代表着0000什么的我忽略了这一点。
当然 得开long long 题目描述不清楚 当然全部开long long会T (我真不容易写一道题。注意不要全开long long 常数是int的两倍 于是把for里的改成int就过了。
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define R register #define db double #define max(x,y) ((x)>(y)?(x):(y)) using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=301,maxn=8; ll n,k,ans; int a[MAXN],p[1<<maxn]; int s[1<<maxn]; ll f[MAXN][MAXN][1<<maxn]; //f[i][j][k] 表示由区间i到区间j合成后状态为k的最大值 signed main() { //freopen("1.in","r",stdin); n=read();k=read(); memset(f,0xcf,sizeof(f)); for(int i=1;i<=n;++i)a[i]=read(),f[i][i][a[i]]=0; for(int i=0;i<(1<<k);++i) s[i]=read(),p[i]=read(); for(int len=2;len<=n;++len) { for(int i=1;i<=n-len+1;++i) { int j=i+len-1; for(int l=i;l<j;++l) { int len2=j-l; if((len2-1)%(k-1))continue; int len1=l-i+1; len1=(len1-1)%(k-1)+1; for(int s1=0;s1<(1<<len1);++s1) { if(f[i][l][s1]<0)continue; if(f[l+1][j][0]>=0) { int state=s1<<1; if(len1==k-1) f[i][j][s[state]]=max(f[i][j][s[state]],f[i][l][s1]+f[l+1][j][0]+p[state]); else f[i][j][state]=max(f[i][j][state],f[i][l][s1]+f[l+1][j][0]); } if(f[l+1][j][1]>=0) { int state=s1<<1|1; if(len1==k-1) f[i][j][s[state]]=max(f[i][j][s[state]],f[i][l][s1]+f[l+1][j][1]+p[state]); else f[i][j][state]=max(f[i][j][state],f[i][l][s1]+f[l+1][j][1]); } } } } } for(int i=0;i<(1<<(k-1));++i) ans=max(ans,f[1][n][i]); printf("%lld\n",ans); return 0; }
LINK:分数规划生成树
其实就是想让我们找出一个最小的生成树罢了,直接prim会比克鲁斯卡尔快一点。注意这里需要二分一下比值因为我们不好直接求出这个比值。
所以考虑先二分,然后权值就有了我们想办法让mid减小的那个方式生成树即可。
//#include<bits/stdc++.h> #include<iostream> #include<queue> #include<iomanip> #include<cctype> #include<cstdio> #include<deque> #include<utility> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<cstdlib> #include<vector> #include<algorithm> #include<stack> #include<map> #include<set> #include<bitset> #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) #define INF 1000000000 #define ll long long #define db double #define EPS 1e-4 #define mp make_pair #define F first #define S second using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=1010; int n; struct wy { int x,y,z; }t[MAXN]; int vis[MAXN]; db a[MAXN][MAXN],d[MAXN]; priority_queue<pair<db,int> > q; inline int abs(int x){return x<0?-x:x;} inline db dis(int x,int y) { return sqrt(1.0*(t[x].x-t[y].x)*(t[x].x-t[y].x)+1.0*(t[x].y-t[y].y)*(t[x].y-t[y].y)); } inline int check(db w) { db ans=0; for(int i=1;i<=n;++i)d[i]=INF*1.0,vis[i]=0; q.push(mp(0,1)); while(q.size()) { int x=q.top().S; if(vis[x]) { q.pop(); continue; } ans=ans-q.top().F; vis[x]=1; for(int i=1;i<=n;++i) { if(vis[i])continue; db dd=abs(t[x].z-t[i].z)-a[x][i]*w; if(d[i]>dd) { d[i]=dd; q.push(mp(-d[i],i)); } } } if(ans<=0)return 1; return 0; } int main() { //freopen("1.in","r",stdin); n=read(); for(int i=1;i<=n;++i) { int x,y,z; x=read();y=read();z=read(); t[i]=(wy){x,y,z}; } for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) a[i][j]=a[j][i]=dis(i,j); db l=0,r=100; while(l+EPS<r) { db mid=(l+r)/2; if(check(mid))r=mid; else l=mid; } printf("%.3lf",l); return 0; }
LINK:pigs有点神奇的题目 看起来很复杂 但其实我还真不会 我有一个做法是拆点然后对于每个人都做一次最大流 但是还是无法实现猪交换的时候的合法性。
做法比较强大 是这样的我们很难完成在存在牛的点的情况下实现牛之间的交换因为考虑这个顺序比较难以控制 可能较早的点会用较晚的点的边所以这很不好写,我们不妨不建牛点 把牛放到一块然后表示牛可以任意互换,这样就不会出现上述情况了。综上我们 每次都将一堆牛直接连向一个人 如果下次还有人连相同的牛的话就把上次连的人连向当前这个人即可。
//#include<bits/stdc++.h> #include<iostream> #include<queue> #include<iomanip> #include<cctype> #include<cstdio> #include<deque> #include<utility> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<cstdlib> #include<vector> #include<algorithm> #include<stack> #include<map> #include<set> #include<bitset> #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) #define INF 1000000000 #define ll long long #define db double #define mp make_pair using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=1010; int n,m,S,T,ans,len=1,t,h; int a[MAXN],b[MAXN]; int lin[MAXN],nex[MAXN*100<<1],ver[MAXN*100<<1],e[MAXN*100<<1]; int q[MAXN],vis[MAXN],cur[MAXN]; inline void add(int x,int y,int z) { ver[++len]=y;nex[len]=lin[x];lin[x]=len;e[len]=z; ver[++len]=x;nex[len]=lin[y];lin[y]=len;e[len]=0; } inline int bfs() { h=t=0; for(int i=1;i<=T;++i)vis[i]=0,cur[i]=lin[i]; q[++t]=S;vis[S]=1; while(h++<t) { int x=q[h]; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(!e[i]||vis[tn])continue; vis[tn]=vis[x]+1; q[++t]=tn; if(tn==T)return 1; } } return 0; } inline int dinic(int x,int flow) { if(x==T)return flow; int rest=flow,k; for(int i=cur[x];i&&rest;i=nex[i]) { int tn=ver[i]; cur[x]=i; if(vis[tn]==vis[x]+1&&e[i]) { k=dinic(tn,min(flow,e[i])); if(!k){vis[tn]=0;continue;} e[i]-=k;e[i^1]+=k;rest-=k; } } return flow-rest; } int main() { //freopen("1.in","r",stdin); m=read();n=read(); S=n+1;T=S+1; for(int i=1;i<=m;++i)a[i]=read(); for(int i=1;i<=n;++i) { int x=read(),cnt=0; for(int j=1;j<=x;++j) { int y=read(); if(b[y])add(b[y],i,INF); else cnt+=a[y]; b[y]=i; } x=read(); add(S,i,cnt); add(i,T,x); } int flow=0; while(bfs())while((flow=dinic(S,INF)))ans+=flow; printf("%d\n",ans); return 0; }
LINK:足球积分 看起来是个贪心。其实应该就是贪心了。很简单对于求最大值我们能赢就赢不要平局因为赢得话比平局得分高 。
输的话刚好相反 分类讨论一下即可。想了一下求最小值还真的有很大的难度 思考良久发现把所有情况搞出来取最小值即可。
很不错的贪心 虽然wa了很多次 下次在提交之前应该手玩一些数据才对。细节 有点ex 不过剩下的就没什么了。
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<ctime> #include<cmath> #include<cctype> #include<algorithm> #include<cstdlib> #include<queue> #include<stack> #include<set> #include<bitset> #include<vector> #include<map> #include<deque> #include<utility> #define INF 1000000010 #define min(x,y) ((x)>(y)?(y):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define mp make_pair #define ll long long using namespace std; char *fs,*ft,buf[1<<15]; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const ll MAXN=100010,maxn=MAXN<<1; ll x,y,n; ll ans,ans1; signed main() { //freopen("1.in","r",stdin); while(scanf("%d%d%d",&x,&y,&n)==3) { ans=ans1=0; ll xx=x,yy=y,nn=n; if(n==1) { if(x>y)ans=ans1=3; if(x==y)ans=ans1=1; printf("%lld %lld\n",ans,ans1); continue; } //先求最大值 ll w=n-1; ll v=min(w,x); ans+=v*3; w-=v; x-=v; if(x>y)ans+=3; if(x==y)++ans; ans+=w; printf("%lld ",ans); //再求最小值 if(xx>yy) { ans1+=3; --nn; v=min(yy,nn); nn-=v; ans1+=nn; printf("%lld\n",ans1); continue; } --nn; if(xx==yy) { if(nn>=3&&yy>=3) { ans1+=3; v=min(yy,nn); nn-=v; ans1+=nn; printf("%lld\n",ans1); } else { ++ans1; ans1+=nn; printf("%lld\n",ans1); } } else//对于 xx<yy 可以考虑把所有情况表示出来取min { ll w1=3,w2=1,w3=0; w1=w1+(nn-min(nn,yy)); yy-=xx; w2=w2+(nn-min(nn,yy)); --yy; w3=w3+(nn-min(nn,yy)); ans1=min(w1,w2); ans1=min(ans1,w3); printf("%lld\n",ans1); } } //cout<<w1<<' '<<w2<<' '<<w3<<endl; return 0; }
LINK:背包的方案数问题。 求不适用第一个物品且装满背包的方案数 再求不用第二个物品的方案数 一直求到第n个?
一个比较显然的思路是暴力 n^2m的暴力 考虑优化这个过程。考虑 我们每次把有用的东西保存下来。也就是说 我们先对整体进行一次01背包方案数 然后 想办法求出第一个除去第一个物品的方案数 观察 把dp式加法换成减法即可。考虑一下实际的意义。
我们先广义的分析:做背包 和 物品的顺序无关 所以我们可以想象成第一个物品是最后一个加进去的 所以可以看出 这样的做法显然正确。
狭义的想:这很显然。
当时我写的无脑暴力 还是保存一下吧 。
#include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<iomanip> #include<algorithm> #include<ctime> #include<map> #include<vector> #include<stack> using namespace std; 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*10+ch-'0';ch=getchar();} return x*f; } const int maxn=1014; int n,m; int w[maxn],f[10007],ans=0; int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(int i=1;i<=n;i++)w[i]=read(),ans+=w[i]; f[0]=1; for(int k=1;k<=n;k++) { memset(f,0,sizeof(f)); f[0]=1; for(int i=1;i<=n;i++) { if(ans-w[i]<m)break; for(int j=m;j>=w[i];j--) { if(i==k)continue; f[j]=(f[j]%maxn+f[j-w[i]]%maxn)%maxn; } } printf("%d ",f[m]%maxn); if(k==n)printf("\n"); } return 0; }
这是 很顺利的 倒序背包清空一下即可。
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000010 #define ll long long #define mp(x,y) make_pair(x,y) #define un unsigned #define db double #define EPS 1e-5 #define mod 1014 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=10010; int n,m; int a[MAXN],f[MAXN],w[MAXN]; int main() { //freopen("FILE.in","r",stdin); //freopen("FILE.out","w",stdout); n=read();m=read(); f[0]=1; for(int i=1;i<=n;++i)a[i]=read(); for(int i=1;i<=n;++i) for(int j=m;j>=a[i];--j) f[j]=(f[j]+f[j-a[i]])%mod; //for(int i=1;i<=m;++i)cout<<f[i]<<endl; for(int i=1;i<=n;++i) { if(m<a[i]){printf("%d\n",f[m]);continue;} for(int j=0;j<=m;++j)w[j]=f[j]; for(int j=a[i];j<=m;++j)f[j]=(f[j]-f[j-a[i]])%mod; printf("%d ",((f[m]+mod)%mod)); for(int j=0;j<=m;++j)f[j]=w[j]; } return 0; }
LINK:最大速度 中级搜索。 这题 搜索起来比较麻烦。
由于到达每个点还有方向考虑把方向也存起来 考虑dij 开跑 但是连续转两次也算掉头 所以还不免记录上次左转还是右转。
注意 大根堆别写错 方向别搞错 预处理要仔细 dij 换成spfa 我们发现 有状态都是0 但是一个0状态能更新一个0状态 所以只能spfa了..
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000010 #define ll long long #define mp(x,y) make_pair(x,y) #define un unsigned #define db double #define EPS 1e-5 #define mod 1014 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=31; int st,en,op,s1,s2,vv,n,m,cnt,ans; int dx[5]={0,1,-1,0,0}; int dy[5]={0,0,0,1,-1}; int f[MAXN][MAXN][5],fa[MAXN*MAXN]; int vis[MAXN][MAXN][5],id[MAXN][MAXN]; int d[5][5][2],v[5][5],g[5][5]; struct wy { int x,y,op; int v,pre; int friend operator <(wy a,wy b) { return a.v<b.v; } }; char a[MAXN][MAXN]; priority_queue<wy> q; inline int getfather(int x){return x==fa[x]?x:fa[x]=getfather(fa[x]);} inline void bfs() { while(q.size()) { wy s=q.top();q.pop(); //if(vis[s.x][s.y][s.op])continue; //vis[s.x][s.y][s.op]=1; for(int i=1;i<=4;++i) { int xx=s.x+d[s.op][i][0]; int yy=s.y+d[s.op][i][1]; if(xx<1||yy<1||xx>n||yy>m)continue; if(a[xx][yy]=='.')continue; int w=max(0,s.v+v[s.op][i]); if(g[s.op][i]==s.pre&&s.pre>0)w=0; if(f[xx][yy][i]<w) { f[xx][yy][i]=w; q.push((wy){xx,yy,i,w,g[s.op][i]}); } } } } int main() { //freopen("1.in","r",stdin); n=read();m=read();vv=read(); memset(f,0xcf,sizeof(f)); for(int i=1;i<=n;++i) { scanf("%s",a[i]+1); for(int j=1;j<=m;++j) { id[i][j]=++cnt;fa[cnt]=cnt; if(a[i][j]!='.'&&a[i][j]!='#'&&a[i][j]!='F') { if(a[i][j]=='E')op=1; if(a[i][j]=='W')op=2; if(a[i][j]=='N')op=3; if(a[i][j]=='S')op=4; st=i,en=j; } if(a[i][j]=='F')s1=i,s2=j; } } for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) { if(a[i][j]=='.')continue; for(int k=1;k<=4;++k) { int x=i+dx[k]; int y=j+dy[k]; if(x<1||y<1||x>n||y>m)continue; if(a[x][y]=='.')continue; int xx=getfather(id[x][y]); int yy=getfather(id[i][j]); fa[xx]=yy; } } if(getfather(fa[id[st][en]])!=getfather(fa[id[s1][s2]])) { puts("-1");return 0; } f[st][en][op]=vv; d[1][1][0]=0;d[1][1][1]=1;v[1][1]=1;g[1][1]=0; d[1][2][0]=0;d[1][2][1]=0;v[1][2]=-INF;g[1][2]=-1; d[1][3][0]=0;d[1][3][1]=0;v[1][3]=-35;g[1][3]=1;//向左 d[1][4][0]=0;d[1][4][1]=0;v[1][4]=-40;g[1][4]=2;//向右 d[2][1][0]=0;d[2][1][1]=0;v[2][1]=-INF;g[2][1]=-1; d[2][2][0]=0;d[2][2][1]=-1;v[2][2]=1;g[2][2]=0; d[2][3][0]=0;d[2][3][1]=0;v[2][3]=-40;g[2][3]=2; d[2][4][0]=0;d[2][4][1]=0;v[2][4]=-35;g[2][4]=1; d[3][1][0]=0;d[3][1][1]=0;v[3][1]=-40;g[3][1]=2; d[3][2][0]=0;d[3][2][1]=0;v[3][2]=-35;g[3][2]=1; d[3][3][0]=-1;d[3][3][1]=0;v[3][3]=1;g[3][3]=0; d[3][4][0]=0;d[3][4][1]=0;v[3][4]=-INF;g[3][4]=-1; d[4][1][0]=0;d[4][1][1]=0;v[4][1]=-35;g[4][1]=1; d[4][2][0]=0;d[4][2][1]=0;v[4][2]=-40;g[4][2]=2; d[4][3][0]=0;d[4][3][1]=0;v[4][3]=-INF;g[4][3]=-1; d[4][4][0]=1;d[4][4][1]=0;v[4][4]=1;g[4][4]=0; q.push((wy){st,en,op,vv,-2}); bfs(); for(int i=1;i<=4;++i)ans=max(ans,f[s1][s2][i]); printf("%d\n",ans); return 0; }
LINK:序列游戏 疯狂的刷题之中尽量不看题解TAT。
两种问题 1 输出字典序第k小的序列 2 输出某个全排列 的字典序编号。
也就是两个问题 刚好互反。询问有10000次 但序列的长度只有20。
遇到这种序列问题先分析一下数字典序需要什么东西QuQ 因为没有性质做题 很难受的。
我想了半天逆序对 但我觉得和逆序对无关关键是字典序。值得一提的是这里序列都是全排列。
先思考一下Q1 无疑 字典序第k小 字典序之和当前数字大小有关 那么我们逐位考虑 对于当前位置i 能放i就放 不能放的话考虑 其放哪qwq
逐位考虑有效 如果当前位不行 那就 减小了 多次阶乘的方案 这样考虑下去即可。
重点是第二问 如何导出 还是逐位考虑 然后发现 了一些小性质 对于第一个位置 不合法的话我们把所有对后面的影响都减掉 默认后面全部都是单调递减的。
再继续向后面扫如果不是单调递减的 我们还需要加回来 然后逐位判断是否加即可。
#//#include<bits/stdc++.h> #include<iostream> #include<queue> #include<iomanip> #include<cctype> #include<cstdio> #include<deque> #include<utility> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<cstdlib> #include<vector> #include<algorithm> #include<stack> #include<map> #include<set> #include<bitset> #define INF 1000000000 #define ll long long #define db double #define pb push_back #define un unsigned using namespace std; inline ll read() { ll 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*10+ch-'0';ch=getchar();} return x*f; } const ll MAXN=25; ll n,Q,k,ans; char a[2]; ll b[MAXN],fac[MAXN],vis[MAXN]; signed main() { //freopen("1.in","r",stdin); n=read();Q=read(); fac[n+1]=1; for(ll i=n;i;--i)fac[i]=fac[i+1]*(n-i+1); for(ll i=1;i<=Q;++i) { scanf("%s",a+1); if(a[1]=='P') { k=read(); memset(vis,0,sizeof(vis)); for(ll j=1;j<=n;++j) { for(ll m=1;m<=n;++m) { if(vis[m])continue; if(fac[j+1]>=k) { vis[m]=1; b[j]=m; break; } else k-=fac[j+1]; } } for(ll j=1;j<=n;++j)printf("%lld ",b[j]); puts(""); } else { ll flag=0;ans=0; for(ll j=1;j<=n;++j) { b[j]=read(); if(b[j]!=j&&!flag){flag=j;ans+=(b[j]-j+1)*fac[j+1];} } if(!flag){puts("1");continue;} memset(vis,0,sizeof(vis)); for(ll j=1;j<=n;++j) { if(j>flag) { ll w=0; for(ll m=b[j];m<=n;++m) { if(vis[m])continue; ++w; } ans-=(w-1)*fac[j+1]; } vis[b[j]]=1; } printf("%lld\n",ans); } } return 0; }
LINK:在线查询第k大 在线查询第k大 每次针对全局找第k大 这个很好写吧 然后其实就是单点修改了。
我再一次手懒的没写splay 直接主席树水了 我好菜啊 连平衡树都 码不出来 /kk
//#include<bits/stdc++.h> #include<iostream> #include<queue> #include<iomanip> #include<cctype> #include<cstdio> #include<deque> #include<utility> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<cstdlib> #include<vector> #include<algorithm> #include<stack> #include<map> #include<set> #include<bitset> #define INF 1000000000 #define inf 1000000000 #define ll long long #define mod 1000000007 #define db double #define pb push_back #define un unsigned #define l(p) t[p].l #define r(p) t[p].r #define sum(p) t[p].sum using namespace std; 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=100010; int n,id,Q; int a[MAXN],maxx=INF,minn,rt; char b[2]; struct wy { int l,r; int sum; }t[MAXN*30<<1]; inline void insert(int &p,int l,int r,int x,int w) { if(!p)p=++id; if(l==r){sum(p)+=w;return;} int mid=(l+r)>>1; if(x<=mid)insert(l(p),l,mid,x,w); else insert(r(p),mid+1,r,x,w); sum(p)=sum(l(p))+sum(r(p)); } inline int ask(int p,int l,int r,int k) { if(sum(p)<k)return -1; if(l==r)return l; int mid=(l+r)>>1; if(sum(r(p))>=k)return ask(r(p),mid+1,r,k); return ask(l(p),l,mid,k-sum(r(p))); } signed main() { //freopen("1.in","r",stdin); n=read(); for(int i=1;i<=n;++i)a[i]=read(),insert(rt,minn,maxx,a[i],1); Q=read(); for(int i=1;i<=Q;++i) { scanf("%s",b+1); int x=read(),y; if(b[1]=='A') { y=read(); insert(rt,minn,maxx,a[x],-1); a[x]-=y; if(a[x]<=0)continue; insert(rt,minn,maxx,a[x],1); } if(b[1]=='C') { y=read(); insert(rt,minn,maxx,a[x],-1); a[x]+=y; insert(rt,minn,maxx,a[x],1); } if(b[1]=='Q')printf("%d\n",ask(rt,minn,maxx,x)); } printf("%d\n",sum(1)); return 0; }
我靠???我电脑关机了 写了一堆东西没了 自闭 3min。
LINK:一个巨难无比的区间dp 写这个题 整个人都快死了 不过打代码的 感觉还是挺不错的。
这道题 我想了一些状态都是无法完成一些操作 考虑正解 设f[i][j][k]表示在i到j这个区间前加k个ai珠子的最小代价。
这个状态是必要的消掉柱子的时候就两端拼起来就是在某个地方添柱子的 决策 所以这个状态是必要的 。
然后 值得注意的是消掉柱子我们只需要判断消掉中间的一段然后两端连起来就行了 然后 两端具体一点是指 1个球和后面的一段 因为区间的决策具有最优性此时枚举决策一段和一个球可以近乎看成等效 所以 这样转移的dp是正确的。
由于没有明显的递推关系 所以需要记搜一下。
//#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<queue> #include<deque> #include<cmath> #include<ctime> #include<cstdlib> #include<stack> #include<algorithm> #include<vector> #include<cctype> #include<utility> #include<set> #include<bitset> #include<map> #define INF 1000000000 #define ll long long #define min(x,y) ((x)>(y)?(y):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define RI register ll #define db double #define pii pair<ll,ll> #define mk make_pair using namespace std; char *fs,*ft,buf[1<<15]; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=110; int n,K; int f[MAXN][MAXN][5];//f[i][j][k]表示在i到j这个区间前加k个ai珠子的最小代价 int a[MAXN]; inline int dfs(int l,int r,int k)//描述整个状态 { if(f[l][r][k]!=-1)return f[l][r][k]; if(l==r)return f[l][r][k]=K-(k+1); if(l>r)return 0; int ans=INF; if(k+1==K)ans=min(ans,dfs(l+1,r,0));//发现可以消掉 if(k+1<K)ans=min(ans,dfs(l,r,k+1)+1);//原地 加一个球 for(int i=l+1;i<=r;++i) if(a[l]==a[i]) ans=min(ans,dfs(l+1,i-1,0)+dfs(i,r,min(K-1,k+1))); return f[l][r][k]=ans; } int main() { //freopen("1.in","r",stdin); n=read();K=read(); memset(f,-1,sizeof(f)); for(int i=1;i<=n;++i)a[i]=read(); printf("%d\n",dfs(1,n,0)); return 0; } /** * ┏┓ ┏┓+ + * ┏┛┻━━━┛┻┓ + + * ┃ ┃ * ┃ ━ ┃ ++ + + + * ████━████+ * ◥██◤ ◥██◤ + * ┃ ┻ ┃ * ┃ ┃ + + * ┗━┓ ┏━┛ * ┃ ┃ + + + +Code is far away from * ┃ ┃ + bug with the animal protecting * ┃ ┗━━━┓ 神兽保佑,代码无bug * ┃ ┣┓ * ┃ ┏┛ * ┗┓┓┏━┳┓┏┛ + + + + * ┃┫┫ ┃┫┫ * ┗┻┛ ┗┻┛+ + + + */
HPU 某题放在这里好了
LINK:Experiment 首先求友好度 显然是单调栈 然后求结合度 这个还是挺套路的 异或不等式是不成立的 所以 需要在trie树上乱搞。
每次走完全契合的道路然后累加即可 有点思维 。
//#include<bits\stdc++.h> #include<cstdio> #include<iomanip> #include<iostream> #include<cmath> #include<ctime> #include<cctype> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<utility> #include<queue> #include<deque> #include<vector> #include<stack> #include<bitset> #include<map> #include<set> #define ull unsigned long long #define ll long long using namespace std; 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=100010; int T,n,id,rt1,ans,sum; int h[MAXN],M; int s[MAXN],t[MAXN*25][2],sz[MAXN*25],top; int f1[MAXN],f2[MAXN]; inline void insert(int rt,int x,int depth) { if(depth<0) { ++sz[rt]; //cout<<rt<<endl; return; } int w=(x&(1<<depth))?1:0; if(!t[rt][w])t[rt][w]=++id; insert(t[rt][w],x,depth-1); sz[rt]=sz[t[rt][0]]+sz[t[rt][1]]; } inline void ask(int rt,int x,int depth) { if(!rt)return; if(depth<0) { sum+=sz[rt]; return; } int w=(x&(1<<depth))?1:0; int c=(M&(1<<depth))?1:0; if(!c) { if(w) { sum+=sz[t[rt][0]]; ask(t[rt][w],x,depth-1); } else { sum+=sz[t[rt][1]]; ask(t[rt][w],x,depth-1); } } else ask(t[rt][w^1],x,depth-1); } inline void cle(int x) { if(t[x][1])cle(t[x][1]); if(t[x][0])cle(t[x][0]); sz[t[x][1]]=sz[t[x][0]]=0; t[x][1]=t[x][0]=0; } int main() { //freopen("1.in","r",stdin); T=read(); while(T--) { n=read();M=read(); for(int i=1;i<=n;++i)h[i]=read(); ans=top=0;rt1=id=1;cle(1); for(int i=1;i<=n;++i) { while(top&&h[s[top]]<=h[i])--top; if(top)f1[i]=h[s[top]]; else f1[i]=0; s[++top]=i; } top=0; for(int i=n;i>=1;--i) { while(top&&h[s[top]]<=h[i])--top; if(top)f2[i]=h[s[top]]; else f2[i]=0; s[++top]=i; } for(int i=1;i<=n;++i) { f1[i]=max(f1[i],f2[i]); insert(rt1,f1[i],20); } for(int i=1;i<=n;++i) { sum=0;ask(rt1,f1[i],20); if(f1[i]+sum>=i+h[i])++ans; //cout<<sum<<endl; } printf("%d\n",ans); } return 0; }
//#include<bits\stdc++.h> #include<iostream> #include<iomanip> #include<cstdio> #include<cstring> #include<string> #include<ctime> #include<cmath> #include<cctype> #include<cstdlib> #include<queue> #include<deque> #include<stack> #include<vector> #include<algorithm> #include<utility> #include<bitset> #include<set> #include<map> #define ll long long #define db double #define ld long double #define put(x) printf("%d\n",x) #define EPS 1e-8 #define mod 998244353 #define INF 1000000010 using namespace std; char *fs,*ft,buf[1<<15]; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll 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*10+ch-'0';ch=getchar();} return x*f; } const ll MAXN=50; ll n,m,I; ll c[MAXN][MAXN]; ll ans; ll a[MAXN]; int main() { freopen("1.in","r",stdin); n=read();m=read();I=read(); c[0][0]=1; for(ll i=1;i<=n;++i) { c[i][0]=1; for(ll j=1;j<=i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j]; } for(ll i=0;i<=m;++i)//n个位数选出i个数的方案数 ans+=c[n][i]; I=ans-I+1;//第i大的数字 for(ll i=n;i>=1;--i) { ll res=0; for(ll j=m-1;j>=0;--j) res+=c[i-1][j]; if(res>=I)a[i]=1,--m; else I-=res; } for(ll i=n;i>=1;--i)printf("%d",a[i]); return 0; }
口胡错了qwq 想了一下发现是 组合数没有什么隔板法...
LINK:成绩单 这个题目是duoxiaoOJ 好像是联合搞的一个OJ 账号是:jzyz2021_chdy 密码大家都知道这里不再赘述/cy
这道题的意思是 每次可以选择一个区间删除 删除的代价是 b*(maxx-minn)*(maxx-minn)+a 删除完了之后两边的区间合并起来问删除完这整个区间的最小代价 n<=50,我们普通的f[i][j]表示删除i到j这个区间所花费的最小代价是解决不了这个问题的 因为我们的一个区间i~j可能删除了很多的单点最后剩下出一些值再删 但是我们借此也可以发现到之所以剩下一些值他们的值域必然是l~r的时候一起删最好 所以对于一个区间来说我们删除一些区间使其值域变为l~r之后是最优的 所以我们再多开两维 f[i][j][l][r]表示当前区间i~j 删除一些元素后值域为l~r的最小代价 特别的我们还需要删除完整个区间所花费的最小代价 f[i][j][0][0]就是一个很好的选择 当然还可以选择放在其他位置.
于是转移的话简单思考一下存在一个k使得我们进行最优的转移 显然 所以枚举k即可 很妙的区间dp.
//#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<ctime> #include<cstdlib> #include<cctype> #include<utility> #include<queue> #include<vector> #include<stack> #include<deque> #include<algorithm> #include<cmath> #include<iomanip> #include<bitset> #include<map> #include<set> #define ll long long #define INF 1000000000 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=51; int n,a,b,cnt; int s[MAXN],w[MAXN],f[MAXN][MAXN][MAXN][MAXN]; //f[i][j][l][r]表示i~j这个区间删到值域为l~r的时候的最小代价. inline void discrete() { sort(s+1,s+1+n); for(int i=1;i<=n;++i)if(i==1||s[i]!=s[i-1])s[++cnt]=s[i]; for(int i=1;i<=n;++i)w[i]=lower_bound(s+1,s+1+cnt,w[i])-s; } int main() { freopen("report.in","r",stdin); freopen("report.out","w",stdout); memset(f,0x3f,sizeof(f)); n=read();a=read();b=read(); for(int i=1;i<=n;++i)s[i]=w[i]=read(); discrete(); for(int i=1;i<=n;++i) { f[i][i][0][0]=a; for(int j=1;j<=w[i];++j) for(int k=w[i];k<=cnt;++k) f[i][i][j][k]=0; } for(int len=2;len<=n;++len) { for(int i=1;i<=n-len+1;++i) { int j=i+len-1; //if(i==5&&j==8)cout<<"ww"<<endl; int maxx=-INF,minn=INF; for(int k=i;k<=j;++k)maxx=max(maxx,w[k]),minn=min(minn,w[k]); for(int l=1;l<=minn;++l) for(int r=maxx;r<=cnt;++r) f[i][j][l][r]=0; for(int l=minn;l<=maxx;++l) { for(int r=l;r<=maxx;++r) { for(int k=i;k<j;++k) { int w=min(f[i][k][l][r],f[i][k][0][0]); w+=min(f[k+1][j][l][r],f[k+1][j][0][0]); f[i][j][l][r]=min(f[i][j][l][r],w); } f[i][j][0][0]=min(f[i][j][0][0],f[i][j][l][r]+(s[r]-s[l])*(s[r]-s[l])*b+a); } } } } //cout<<f[1][4][4][4]<<endl; //cout<<f[5][8][3][4]<<endl; printf("%d\n",f[1][n][0][0]); return 0; }
LINK:高手训练01背包 还是duoxiao OJ的一道题 我咋啥dp都不会写Y...
$n<=10^6$ $m<=1^5$ $s<=300$ $v<=10^9$ 其中n是物品的个数 m是背包大小 s为物品的体积 v为物品的价值..
显然的思路是直接暴力01背包 复杂度nm爆掉 考虑优化 01背包的复杂度下界就是nm ...考虑问题的特异性 显然 s很小n很大 那么我们可以抽象成只有300种体积不一的物品 但是不同个数的物品带来的代价不同 这就好比变形的多重背包 考虑把复杂度优化到 ms 这个其实和多重背包的问题很像 我们想一下多重背包是怎么写的。
多重背包 f[j]=max{f[k]+v[i][j-k]} 我们发现了什么 这个东西是可以单调队列优化的 我们对于上述问题求出相应的 v数组即可.貌似这个东西 要开vector...
啊啊啊 多重背包咋写来着 自闭了 不过这次一定要自己写!有一些小尴尬 这个状态转移的单调性我无法保证 这题好诡异...咕掉
LINK:高手训练保龄球 这题更有意思 一个大小为n的区间有k个框每个框大小为w 框和框之间可以重叠 且1的左边有一个空位 n的右边也有一个空位 每个位置上有一个值ai求框能框住的最大值? 考虑一下简化问题 框没有大小 其实就是取k个最大子段和 这个我记得可以模拟费用流 线段树维护当然正常的话如何dp?f[i][j][0/1]表示到了第i个位置用了j个框此时第i个数选还是不选,获得一个nk复杂度的dp 考虑框有限制的时候怎么办?f[i][j][k]表示此时到了第i个位置用了j个框此时第j个框还需要框住k个位置的方案数,这个状态是 nkw的$5\cdot 10^8$再乘个T就GG了当然这个状态看起来很不对劲因为空间已经炸掉了 先考虑转移吧 f[i][j][k-1]=max{f[i-1][j-1][w]+a[i]}; 当然还有一个转移 f[i][j][k]=f[i][j][k+1]; 关于这个w的枚举我们直接上单调性优化即可 空间滚动一波 刚开始的那个直接虚设两个位置即可 复杂度是5e9的 好像过不去 我再想想?
LINK:高手训练动漫排序comic 一棵树 从1为根从1开始排序每个点的儿子有先后顺序且所有儿子必须排在父亲后面兄弟间重要度排序问有多少种排序方案?
还是很容易思考出来的 我们从简单的情况开始考虑一个点内部已经知道了方案数了 这些方案数的实质性意义其实是一堆排列不同且合法的方案 当轮到这个点的时候这个点必然是放在整个排列从左往右第一个空位上的 其他儿子呢 在有固定的顺序下其实发现和其他点是没有关系的这其实就是一个组合数了递推出组合数预处理即可解决这个问题。
//#include<bits/stdc++.h> #include<algorithm> #include<iostream> #include<iomanip> #include<cstring> #include<cstdlib> #include<utility> #include<cctype> #include<string> #include<bitset> #include<vector> #include<cstdio> #include<ctime> #include<cmath> #include<queue> #include<deque> #include<stack> #include<list> #include<set> #include<map> #define INF 1000000000 #define ll long long #define mod 10007 #define pb push_back using namespace std; char *fs,*ft,buf[1<<15]; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } 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*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=1010; int T,n; int f[MAXN],sz[MAXN];//f[i]表示以i为根的子树内的排序方案 int C[MAXN][MAXN]; vector<int>g[MAXN]; inline void dp(int x) { int w=1;sz[x]=1;f[x]=1; for(unsigned i=0;i<g[x].size();++i) { int tn=g[x][i]; dp(tn); sz[x]+=sz[tn]; } for(unsigned i=0;i<g[x].size();++i) { int tn=g[x][i]; //当前剩余 sz[x]-w-1个位置 选出sz[tn]-1个位置 f[x]=(f[x]*f[tn]%mod*C[sz[x]-w-1][sz[tn]-1])%mod; w+=sz[tn]; } } int main() { //freopen("comic.in","r",stdin); //freopen("comic.out","w",stdout); T=read();C[0][0]=1; C[1][1]=1;C[1][0]=1; for(int i=2;i<=1000;++i) for(int j=0;j<=i;++j) { if(!j)C[i][j]=1; else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } while(T--) { n=read(); for(int i=1;i<=n;++i) { int x=read();g[i].clear(); for(int j=1;j<=x;++j)g[i].pb(read()); } dp(1); printf("%d\n",f[1]); } return 0; }