最近做了一堆TYVJ的逗【水】比题,在这里整理一下。。。
【虽然似乎好像没意义】
立体图
惊天大模拟,搞一个数组,从正中间开始画。
顺便搞个函数,一次可以画一个方块,从后到前,从下到上画就行了
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=105; int a[N][N],n,m; char map[4000][4000]; int mxx,mxy,mny,mnx; void print(int x,int y) { mxx=max(mxx,x);mnx=min(mnx,x-5); mxy=max(mxy,y+6);mny=min(mny,y); map[x][y]=map[x][y+4]='+';map[x][y+1]=map[x][y+2]=map[x][y+3]='-'; x--; map[x][y]=map[x][y+4]='|';map[x][y+1]=map[x][y+2]=map[x][y+3]=' ';map[x][y+5]='/'; x--; map[x][y]=map[x][y+4]='|';map[x][y+1]=map[x][y+2]=map[x][y+3]=' ';map[x][y+5]=' ';map[x][y+6]='+'; x--; map[x][y]=map[x][y+4]='+';map[x][y+1]=map[x][y+2]=map[x][y+3]='-';map[x][y+5]=' ';map[x][y+6]='|'; x--; map[x][y+1]=map[x][y+5]='/';map[x][y+2]=map[x][y+3]=map[x][y+4]=' ';map[x][y+6]='|'; x--; map[x][y+2]=map[x][y+6]='+';map[x][y+3]=map[x][y+4]=map[x][y+5]='-'; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)scanf("%d",&a[i][j]); mxx=mxy=1,mny=mnx=3000; for(int i=0;i<=3100;i++) for(int j=0;j<=3100;j++)map[i][j]='.'; int xx=1500,yy=1500;//绘制中心点 for(int i=1;i<=n;i++) { int x=xx,y=yy; for(int j=1;j<=m;j++) { for(int k=1;k<=a[i][j];k++)//从上往下搞一搞 print(xx,yy),xx-=3; yy+=4,xx=x;//横着搞一搞 } yy=y-2,xx=x+2; } for(int i=mnx;i<=mxx;i++){ for(int j=mny;j<=mxy;j++) printf("%c",map[i][j]);puts("");} }
尼克的任务
我用了spfa无耻的水过了这个题。
对于每个任务[l,l+t-1] 连边l,l+t 边权t
对于某个点没有开始的任务 连边i,i+1 边权0
最后求一遍最短路 答案=n-最短路
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<queue> #include<cmath> #include<vector> using namespace std; const int N=10005,M=N*5; int list[M],head[N],nxt[M],len[M],n,k,tot; bool ok[N]; void add(int a,int b,int c) { tot++; list[tot]=b,len[tot]=c; nxt[tot]=head[a]; head[a]=tot; } bool vis[N]; int dist[N]; void spfa() { memset(dist,0x3f,sizeof dist); queue<int>q;dist[1]=0,q.push(1),vis[1]=1; while(!q.empty()) { int v=q.front();q.pop(); vis[v]=0; for(int i=head[v];i;i=nxt[i]) { int u=list[i]; if(dist[u]>dist[v]+len[i]) { dist[u]=dist[v]+len[i]; if(!vis[u]) vis[u]=1,q.push(u); } } } printf("%d\n",n-dist[n+1]); } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=k;i++) { int l,t; scanf("%d%d",&l,&t); ok[l]=1;add(l,l+t,t); } for(int i=1;i<=n;i++) if(!ok[i])add(i,i+1,0); spfa(); }
表达式计算4
这题不说什么了。。。。先把括号搞一搞
再中缀转一下前缀或后缀 最后计算。。。
代码太难看。。。就不贴了。。。。
矩阵取数游戏
显然这题可以发现行和列是不相关的 可以直接把每行拎出来做
设f[i][j]代表总共选了i个数 左边的选了j个的答案
则
f[i][j]=max(f[i-1][j-1]+a[j]*pow(2,i),f[i-1][j]+a[m-(i-j)+1]*pow(2,i));
然后套上高精度模板。。
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int N=100; int n,m; struct num {int a[N],s;}f[N][N],t,a[N],two,zero; num operator +(num a,num b) { memset(&t,0,sizeof t); t.s=max(a.s,b.s); int jin=0; for(int i=1;i<=t.s;i++) { jin+=a.a[i]+b.a[i]; t.a[i]=jin%10000; jin=jin/10000; } while(jin)t.a[++t.s]=jin%10000,jin/=10000; return t; } num operator *(num a,num b) { memset(&t,0,sizeof t); t.s=a.s+b.s; int jin=0; for(int i=1;i<=a.s;i++) { for(int j=1;j<=b.s;j++) { jin+=a.a[i]*b.a[j]+t.a[i+j-1]; t.a[i+j-1]=jin%10000; jin/=10000; } for(int j=i+b.s;jin!=0;j++) { jin+=t.a[j]; t.a[j]=jin%10000; jin/=10000; } } while(t.s>1&&t.a[t.s]==0)t.s--; return t; } bool operator <(num a,num b) { if(a.s<b.s)return 1; if(a.s>b.s)return 0; for(int i=a.s;i;i--) { if(a.a[i]<b.a[i])return 1; if(a.a[i]>b.a[i])return 0; } return 0; } num max(num a,num b) {return a<b?b:a;} num pow(int p) { num ret=two; for(int i=2;i<=p;i++) ret=ret+ret; return ret; } char s[N]; void output(num &a) { printf("%d",a.a[a.s]); for(int i=a.s-1;i;i--)printf("%04d",a.a[i]); puts(""); } void read(num &a) { scanf("%s",s); int len=strlen(s); reverse(s,s+len); s[len]=s[len+1]=s[len+2]='0'; for(int i=0;i<len;i+=4) a.a[++a.s]=(s[i+3]-'0')*1000+(s[i+2]-'0')*100+(s[i+1]-'0')*10+s[i]-'0'; } num dp() { memset(f,0,sizeof f); for(int i=1;i<=m;i++) for(int j=0;j<=i;j++) { if(j-1>=0) f[i][j]=max(f[i-1][j-1]+a[j]*pow(i),f[i-1][j]+a[m-(i-j)+1]*pow(i)); else f[i][j]=f[i-1][j]+a[m-(i-j)+1]*pow(i); } num ans=zero; for(int i=0;i<=m;i++) ans=max(ans,f[m][i]); return ans; } int main() { scanf("%d%d",&n,&m); zero.s=1,two.s=1,two.a[1]=2; num ans=zero; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) memset(&a[j],0,sizeof a[j]),read(a[j]); ans=ans+dp(); } output(ans); }
过河
一道结论比较神的题。。。
结论:两个石子相差大于100 我们就可以把它们看成100
首先如果S=T 则这是个普及组题 直接特判掉
若在S=T-1下结论成立 则其他的都成立了
我们考虑极端情况S=9 T=10
在mod 9剩余系下 用10可以遍历整个剩余系
不过遍历需要最多走9次 所以需要的长度为90
所以90以上的直接看成90就行了
看成100更是对的啦!
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int N=2000*2000; int l,s,e,m; int a[N],t[N];//i位置是否有石子 每个石子的初始位置 int f[N]; int main() { scanf("%d%d%d%d",&l,&s,&e,&m); for(int i=1;i<=m;i++)scanf("%d",&t[i]); sort(t+1,t+m+1); if(s==e) { int ans=0; for(int i=1;i<=m;i++) if(t[i]%s==0)ans++; printf("%d\n",ans); } else { t[++m]=l;int lst=0;a[0]=1; for(int i=1;i<=m;i++) { if(t[i]-t[i-1]>100)a[lst+100]=1,lst+=100; else a[lst+(t[i]-t[i-1])]=1,lst+=(t[i]-t[i-1]); } for(int i=0;i<=lst;i++)f[i]=10000; f[0]=1; for(int i=0;i<=lst;i++) { for(int j=s;j<=e;j++) f[i+j]=min(f[i+j],f[i]+a[i+j]); } int ans=0x3f3f3f3f; for(int i=lst;i<=lst+e;i++)ans=min(ans,f[i]); printf("%d\n",f[lst]-2); } }
Mobile Service
神奇的DP优化 感觉好厉害
首先暴力的方程肯定是f[t][i][j][k]啦
t时刻 第一个人在i 第二个在j 第三个在k时的答案
超时啦!!!!
考虑优化吧。。。
题意里面说了,有请求人就得过去
所以到i,j,k里面肯定有人在关键点p[t]
t-1时刻肯定也有人在p[t-1]
而且三个人的编号也没有什么太大的意义
然后这么搞
设f[t][i][j]
为t时刻 一个人在i 一个人在j 一个人在p[i]时候的答案
然后乱转移:
f[t][j][p[t-1]]=f[t-1][i][j]+d[i][p[t]];
f[t][i][p[t-1]]=f[t-1][i][j]+d[j][p[t]];
f[t][i][j]=f[t-1][i][j]+d[p[t-1]][p[t]];
大概意思就是i去了 j去了 或者k去了
神奇啊!!!!
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int N=1005; int f[3][205][205]; int p[N],n,l,map[N][N]; void dp() { memset(f,0x3f,sizeof f); f[1%2][1][2]=map[3][p[1]]; f[1%2][1][3]=map[2][p[1]]; f[1%2][2][3]=map[1][p[1]]; for(int i=2;i<=n;i++) { for(int j=1;j<=l;j++) for(int k=1;k<=l;k++) f[i%2][k][j]=0x3f3f3f3f; for(int j=1;j<=l;j++) for(int k=1;k<=l;k++) { f[i%2][k][p[i-1]]=min(f[i%2][k][p[i-1]],f[(i-1)%2][j][k]+map[j][p[i]]); f[i%2][j][p[i-1]]=min(f[i%2][j][p[i-1]],f[(i-1)%2][j][k]+map[k][p[i]]); f[i%2][j][k]=min(f[i%2][j][k],f[(i-1)%2][j][k]+map[p[i-1]][p[i]]); } } int ans=0x3f3f3f3f; for(int i=1;i<=l;i++) for(int j=1;j<=l;j++) ans=min(ans,f[n%2][i][j]); printf("%d\n",ans); } int main() { scanf("%d%d",&l,&n); for(int i=1;i<=l;i++) for(int j=1;j<=l;j++) scanf("%d",&map[i][j]); for(int i=1;i<=n;i++)scanf("%d",&p[i]); dp(); }
新三国争霸
最小生成树+dp
这个一开始难在了dp的阶段划分
如果划分的某个阶段里面有方案更改的话 很难做
所以设f[i]表示i时刻的答案 它能从f[j]转移过来
[i,j]中间没有改方案
所以f[i]=min(f[j]+kru()*(i-j)*v+k);
kru()为在[i,j]符合条件的最小生成树
然后好像似乎找符合条件的比较麻烦
搞一个g数组维护两个点之间能不能连边
倒着做一做就好了
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int N=20000; struct edge { int a,b,c; }s[N]; bool cmp(edge a,edge b) {return a.c<b.c;} int f[55],fa[400],n,m,t,v,k; int find(int x){if(fa[x]!=x)fa[x]=find(fa[x]);return fa[x];} bool g[400][400],no[400][400][55]; int kru() { for(int i=1;i<=n;i++)fa[i]=i; int ans=0,cnt=0; for(int i=1;i<=m;i++) { if(g[s[i].a][s[i].b])continue; int a=find(s[i].a); int b=find(s[i].b); if(a==b)continue; fa[a]=b;ans+=s[i].c;cnt++; } if(cnt<n-1)ans=0x3f3f3f3f; return ans; } void dp() { sort(s+1,s+m+1,cmp); memset(f,0x3f,sizeof f); f[0]=0; for(int i=1;i<=t;i++) { memset(g,0,sizeof g); for(int j=i-1;j>=0;j--) { for(int l=1;l<=m;l++) g[s[l].a][s[l].b]|=no[s[l].a][s[l].b][j+1]; f[i]=min(f[i],f[j]+kru()*(i-j)*v+k); } } printf("%d\n",f[t]); } int main() { scanf("%d%d%d%d%d",&n,&m,&t,&v,&k); for(int i=1;i<=m;i++) scanf("%d%d%d",&s[i].a,&s[i].b,&s[i].c); int p;scanf("%d",&p); for(int i=1;i<=p;i++) { int a,b,t1,t2;scanf("%d%d%d%d",&a,&b,&t1,&t2); t1=min(t1,t),t2=min(t2,t); if(t1>t2)swap(t1,t2); for(int j=t1;j<=t2;j++) no[a][b][j]=no[b][a][j]=1; } dp(); }
有理逼近
唬人题,看上去很可怕,实际上很水。。
可以证【感】明【受】 找出的两个分数的分母 分子相差很【为】小【1】
所以我们找其中一个分数。。然后瞎加加减减一下乱搞。。。
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> #include<queue> using namespace std; double ax,ay,bx,by; int p,n,f1,f2; double now,v; int gcd(int a,int b) {return b==0?a:gcd(b,a%b);} void judge(int x,int y) { if(x<=0||x>n)return; if(y<=0||y>n)return; double now=(x*1.0)/(y*1.0); if(now<v) { if(fabs(now-v)<fabs(v-(ax*1.0)/(ay*1.0))||!f1) ax=x,ay=y,f1=1; } else { if(fabs(now-v)<fabs(v-(bx*1.0)/(by*1.0))||!f2) bx=x,by=y,f2=1; } } void work() { for(int i=1;i<=n;i++)//分mu { double x=i; double y=i/v*1.0; judge(x,y); judge(x-1,y); judge(x+1,y); judge(x-1,y+1); judge(x+1,y-1); judge(x,y+1); judge(x,y-1); judge(x-1,y-1); judge(x+1,y+1); } int g=gcd(ax,ay); ax/=g,ay/=g; g=gcd(bx,by),bx/=g,by/=g; cout<<ax<<"/"<<ay<<" "<<bx<<"/"<<by<<endl; } int main() { scanf("%d%d",&p,&n); v=sqrt(p); work(); }
bomb
一道很容易错的题,会误认为是Dilworth定理。。其实并不是Dilworth定理
Dilworth定理必须用于偏序集 偏序集必须要具备三个性质
自反的、反对称的和可传递的
然而这个玩意并不满足自反 因为是严格单调 自身不可比
所以只能用二分图匹配求第二问。。。。
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> #include<queue> using namespace std; const int N=3000,M=300000; int n,f[N]; int fa[N],vis[N]; struct pos { int x,y,z; }a[N]; int list[M],head[N],nxt[M],tot; void add(int a,int b) { tot++; list[tot]=b; nxt[tot]=head[a]; head[a]=tot; } bool dfs(int v) { for(int i=head[v];i;i=nxt[i]) { int u=list[i]; if(!vis[u]) { vis[u]=1; if(fa[u]==0||dfs(fa[u])) {fa[u]=v;return 1;} } } return 0; } bool cmp(pos a,pos b) { if(a.x==b.x)return a.y<b.y; return a.x<b.x; } bool judge(int i,int j) {return a[i].y>a[j].y&&a[i].z>a[j].z;} void dp() { int ans=0; sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++) { f[i]=1; for(int j=1;j<i;j++) if(judge(i,j))f[i]=max(f[i],f[j]+1); ans=max(ans,f[i]); } printf("%d\n",ans); } void build() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j) { if(a[i].x>a[j].x&&a[i].y>a[j].y&&a[i].z>a[j].z)//能达到 add(i,j); } } void work() { int ans=0; for(int i=1;i<=n;i++) { memset(vis,0,sizeof vis); ans+=dfs(i); } printf("%d\n",n-ans); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z); dp(); build(); work(); }
等差数列
发现好像直接dp会超时。。。然而发现每个数都特别小
就用数字大小直接来作为状态了
设f[i][j]为前i个数,公差为j时的答案。
转移就是的时候注意公差为0的特殊考虑一下。。
要不然会多算
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> #include<set> using namespace std; const int N=1005,d=1005,mod=9901; int f[N][N*3],a[N],n; void dp() { int ans=0; for(int i=1;i<=n;i++)f[i][0]=1; for(int i=2;i<=n;i++) { for(int j=1;j<i;j++) { int k=a[i]-a[j]+d; f[i][k]=(f[i][k]+f[j][k]+(k!=0))%mod; } } for(int i=1;i<=n;i++) for(int j=0;j<=2*d;j++) ans=(ans+f[i][j])%mod; printf("%d\n",ans); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); dp(); }
MM不哭
似乎有好多种做法:
做法1:f[i][j][0/1]表示左边搞了i个,右边搞了j个 在i/j时的答案
做法2:f[i][j][0/1]表示只搞[i,j],在i/j时的答案
我用的做法2
其实这个做法2是个区间dp 只不过O(1)转移
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=2005; long long f[N][N][2],sum[N];//1左边 2右边 int n,c; struct pos { int a,b,c; }s[N]; bool cmp(pos a,pos b) {return a.a<b.a;} long long d(int l,int r) {return abs(s[l].a-s[r].a);} long long w(int l,int r) { if(l>r)swap(l,r); return sum[n]-sum[r]+sum[l-1]; } void dp() { for(int len=1;len<n;len++) for(int l=1;l<=n-len;l++) { int r=l+len; f[l][r][0]= min(f[l+1][r][0]+d(l,l+1)*w(l+1,r), f[l][r-1][1]+d(r-1,r)*w(l,r-1)+d(r,l)*w(r,l)); f[l][r][1]= min(f[l][r-1][1]+d(r-1,r)*w(l,r-1), f[l+1][r][0]+d(l,l+1)*w(l+1,r)+d(l,r)*w(l,r)); } printf("%I64d\n",min(f[1][n][1],f[1][n][0])); } int main() { scanf("%d%d",&n,&c); for(int i=1;i<=n;i++)scanf("%d%d",&s[i].a,&s[i].b),s[i].c=i; sort(s+1,s+n+1,cmp); for(int i=1;i<=n;i++)sum[i]=sum[i-1]+s[i].b; memset(f,0x7f,sizeof f); for(int i=1;i<=n;i++) if(s[i].c==c){f[i][i][1]=f[i][i][0]=0;break;} dp(); }
最优贸易
这是普及组的题?????
先在图上dfs一遍 标记出来哪些点能从1走到
然后把这些点扔进队列跑spfa
spfa松弛的时候改成dist[u]#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int N=100005,M=500005*3;
int list[M],head[N],next[M],w[N],tot,n,m;
void add(int a,int b)
{
tot++;
list[tot]=b;
next[tot]=head[a];
head[a]=tot;
}
int list2[M],head2[N],next2[M],w2[N],tot2;
void add2(int a,int b)
{
tot2++;
list2[tot2]=b;
next2[tot2]=head2[a];
head2[a]=tot2;
}
int vis[N],p[N],cnt,dist[N];
void dfs(int v)
{
vis[v]=1;
p[++cnt]=v;
for(int i=head[v];i;i=next[i])
if(!vis[list[i]])dfs(list[i]);
}
void dfs2(int v)
{
vis[v]=1;
p[++cnt]=v;
for(int i=head2[v];i;i=next2[i])
if(!vis[list2[i]])dfs2(list2[i]);
}
void spfa()
{
memset(dist,0x3f,sizeof dist);
memset(vis,0,sizeof vis);
queue<int>q;
for(int i=1;i<=cnt;i++)
dist[p[i]]=0,q.push(p[i]),vis[p[i]]=1;
while(!q.empty())
{
int v=q.front();q.pop();vis[v]=0;
for(int i=head[v];i;i=next[i])
{
int u=list[i];
if(dist[u]<dist[v]+w[u]-w[v])
{
dist[u]=dist[v]+w[u]-w[v];
if(!vis[u])
q.push(u),vis[u]=1;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
for(int i=1;i<=m;i++)
{
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b);if(c==2)add(b,a);
add2(b,a);if(c==2)add2(a,b);
}
dfs(1);
spfa();
memset(vis,0,sizeof vis);cnt=0;
dfs2(n);
int ans=0;
for(int i=1;i<=cnt;i++)
ans=max(ans,dist[p[i]]);
printf("%d\n",ans);
}
在机房静静地看着你刷题,我就给你卡评测机。。。
太神啦!!
蒟蒻留名,另外题目名好逗啊。