【noip 2016】提高组

时间:2023-03-08 18:51:25

D1T1.玩具谜题

题目链接

直接模拟就好了……water。

 #include<cstdio>
int n,m,a,s,ans;
struct node{char name[];int dir;}x[];
int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<n;i++)scanf("%d%s",&x[i].dir,x[i].name);
while(m--)
{
scanf("%d%d",&a,&s);
if(a==x[ans].dir)ans-=s;
else ans+=s;
if(ans<)ans+=n;
ans%=n;
}
printf("%s",x[ans].name);
return ;
}

D1T2.天天爱跑步

题目链接

因为太菜了,至今不会这道题……先扔题目链接,以后再回来把坑填了(flag 我回来填坑辣

s→t的路径可以拆成s→lca和lca→t两条路径,草稿纸上画一画可以推出两条公式:如果点i可以被检测到,则在s→lca这条路径上:deep[i]+w[i]=deep[s];或者在lca→t这条路径上:deep[i]-w[i]=deep[t]-way(路径长度)。

剩下的……写了比较详细的代码注释。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=;
int n,m,u,v,cnt,s,t,anc,way;
int time[N],ans[N],up[N*],dn[N*],c[N];//注意up数组和dn数组都要开两倍
int first[N],deep[N],f[N][];
struct edge{int to,next;}e[N*];//边表开两倍
vector<int>q1[N],q2[N],q3[N];
int read()
{
int x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
void ins(int u,int v){cnt++;e[cnt].to=v;e[cnt].next=first[u];first[u]=cnt;}
void dfs(int x)
{
for(int i=first[x];i;i=e[i].next)
{
int to=e[i].to;
if(deep[to])continue;//节点to已出现过
deep[to]=deep[x]+;
f[to][]=x;//to的父亲为x
dfs(to);
}
}
int lca(int ri,int rj)//倍增求最近公共祖先
{
if(deep[ri]<deep[rj])swap(ri,rj);
int d=deep[ri]-deep[rj];
for(int i=;(<<i)<=d;i++)
if((<<i)&d)ri=f[ri][i];
if(ri==rj)return ri;
for(int i=;i>=;i--)
if((<<i)<=deep[rj]&&f[ri][i]!=f[rj][i])
ri=f[ri][i],rj=f[rj][i];
return f[ri][];
}
void DFS(int x,int last)
{
//向上的桶一律加N是为了防止减的时候出现负数
int u=dn[deep[x]+time[x]],v=up[deep[x]-time[x]+N],sz;
dn[deep[x]]+=c[x];//节点x作为s出现
sz=q1[x].size();
for(int i=;i<sz;i++)up[q1[x][i]+N]++;//点x作为t出现
for(int i=first[x];i;i=e[i].next)if(e[i].to!=last)DFS(e[i].to,x);
ans[x]=dn[deep[x]+time[x]]+up[deep[x]-time[x]+N]-u-v;
//详见之前提到的公式,以及记得减去旧值
sz=q2[x].size();
for(int i=;i<sz;i++){dn[q2[x][i]]--;if(q2[x][i]==deep[x]+time[x])ans[x]--;}
//如果是在lca处被检测到,记得减去重复情况
sz=q3[x].size();
for(int i=;i<sz;i++)up[q3[x][i]+N]--;
//dfs退出lca时,更新两个桶
}
int main()
{
n=read();m=read();
for(int i=;i<n;i++)
{
u=read();v=read();
ins(u,v);ins(v,u);
}
deep[]=;//会影响后面dfs的判断
dfs();
for(int j=;j<=;j++)
for(int i=;i<=n;i++)
f[i][j]=f[f[i][j-]][j-];//递推出f数组
for(int i=;i<=n;i++)time[i]=read();
for(int i=;i<=m;i++)
{
s=read();t=read();
anc=lca(s,t);//最近公共祖先
way=deep[s]+deep[t]-*deep[anc];//路径长度
c[s]++;//节点s作为起点出现的次数+1
q1[t].push_back(deep[t]-way);
q2[anc].push_back(deep[s]);
q3[anc].push_back(deep[t]-way);
//前面提到的两条公式:
//在s→lca这条路径上:deep[i]+w[i]=deep[s];
//在lca→t这条路径上:deep[i]-w[i]=deep[t]-way;
//一个节点对ans的贡献从LCA开始,所以存在LCA的队列里
}
DFS(,);
for(int i=;i<=n;i++)printf("%d ",ans[i]);
return ;
}

D1T3.换教室

题目链接

最短路+期望dp。

数组dp[i][j][k]代表当前进行到第i个时间段,已经更换了j门课程;若k=0则代表当前时间段不更换教室,k=1则代表当前时间段更换教室。

记得最后再循环一下dp[n][j][k],输出最优解。

 #include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
const double inf=1e15;
int n,m,v,e,a,b,c[N],d[N];
double w,k[N],dp[N][N][],dis[N][N];
int read()
{
int x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
void floyd()
{
for(int k=;k<=v;k++)
for(int i=;i<=v;i++)
for(int j=;j<=v;j++)
dis[i][j]=dis[j][i]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main()
{
n=read();m=read();v=read();e=read();
for(int i=;i<=n;i++)c[i]=read();
for(int i=;i<=n;i++)d[i]=read();
for(int i=;i<=n;i++)scanf("%lf",&k[i]);
for(int i=;i<=v;i++)
for(int j=;j<=v;j++)
if(i!=j)dis[i][j]=inf;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
dp[i][j][]=dp[i][j][]=inf;
for(int i=;i<=e;i++)
a=read(),b=read(),scanf("%lf",&w),dis[a][b]=dis[b][a]=min(dis[a][b],w);
floyd();
dp[][][]=;dp[][][]=;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
{
if(j==)dp[i][j][]=min(dp[i][j][],dp[i-][j][]+dis[c[i-]][c[i]]);
else
{
dp[i][j][]=min(dp[i][j][],dp[i-][j][]+dis[c[i-]][c[i]]);
dp[i][j][]=min(dp[i][j][],dp[i-][j][]+dis[d[i-]][c[i]]*k[i-]+dis[c[i-]][c[i]]*(-k[i-]));
dp[i][j][]=min(dp[i][j][],dp[i-][j-][]+dis[c[i-]][d[i]]*k[i]+dis[c[i-]][c[i]]*(-k[i]));
dp[i][j][]=min(dp[i][j][],dp[i-][j-][]+dis[c[i-]][c[i]]*(-k[i-])*(-k[i])+dis[c[i-]][d[i]]*(-k[i-])*k[i]
+dis[d[i-]][c[i]]*k[i-]*(-k[i])+dis[d[i-]][d[i]]*k[i-]*k[i]);
}
}
double ans=inf;
for(int i=;i<=m;i++)
{
ans=min(ans,dp[n][i][]);
ans=min(ans,dp[n][i][]);
}
printf("%.2lf",ans);
return ;
}

D2T1.组合数问题

题目链接

杨辉三角打个表,然后前缀和优化优化(get到新姿势

 #include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=;
long long n,m,t,k,f[N][N],sum[N][N];
long long read()
{
long long x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
int main()
{
t=read();k=read();
for(int i=;i<N;i++)f[i][]=;
for(int i=;i<N;i++)
for(int j=;j<=i;j++)
{
f[i][j]=(f[i-][j]+f[i-][j-])%k;
if(!f[i][j])sum[i][j]=;
sum[i][j]+=sum[i][j-];
}
while(t--)
{
long long ans=;
n=read();m=read();
for(long long i=;i<=n;i++)
ans+=sum[i][min(m,i)];
printf("%lld\n",ans);
}
return ;
}

D2T2.蚯蚓

题目链接

维护三个队列:q1是原数列(先从大到小sort一波),q2是砍完之后的前段,q3是砍完之后的后段(依据题意易得,q2、q3单调递减)。每次从三个队列的队头元素取出最大的一个进行切断操作并在队列中删去它,再将砍完后生成的两个新数分别存入q2、q3。

至于每次操作完增加的长度……直接用时间算就好了✔

 #include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
const int M=;
double p;
int h1=,h2=,t2,h3=,t3;
long long n,m,q,u,v,t;
long long q1[N],q2[M],q3[M];
long long read()
{
long long x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
bool cmp(long long a,long long b){return a>b;}
long long get(int tim)
{
long long x=-0x3f3f3f3f3f3f3f3f;
if(h1<=n&&q1[h1]>x)x=q1[h1];
if(h2<=t2&&q2[h2]>x)x=q2[h2];
if(h3<=t3&&q3[h3]>x)x=q3[h3];
if(h1<=n&&x==q1[h1])h1++;
else if(h2<=t2&&x==q2[h2])h2++;
else h3++;
return x+(tim-)*q;
}
int main()
{
n=read();m=read();q=read();u=read();v=read();t=read();p=1.0*u/v;
for(int i=;i<=n;i++)q1[i]=read();
sort(q1+,q1+n+,cmp);
for(int i=;i<=m;i++)
{
long long k=get(i);
if(i%t==)printf("%lld ",k);
long long k1=floor(k*p),k2=k-k1;
q2[++t2]=k1-q*i;q3[++t3]=k2-q*i;//注意-q*i!
}
printf("\n");
for(int i=;i<=n+m;i++)
{
long long k=get(m+);
if(i%t==)printf("%lld ",k);
}
return ;
}

D3T3.愤怒的小鸟

题目链接

状压dp……注意精度问题。

f[i][j]代表当第i只小猪与第j只小猪配对时,小鸟的飞行轨迹可以消灭的小猪的状态。处理完就先枚举当前状态,然后枚举第一只尚未配对的小猪,再枚举与其配对的小猪,直接dp。

注意:有可能一只小鸟只消灭了一只小猪。

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int T,n,m,id[],f[][],dp[<<];
double a,b,x[],y[];
bool pan(double x,double y){return fabs(x-y)<1e-;}
void solve()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)scanf("%lf%lf",&x[i],&y[i]);
memset(f,,sizeof(f));
for(int i=;i<=n;i++)
for(int j=i+;j<=n;j++)
{
if(pan(x[i],x[j]))continue;
a=(y[j]/x[j]-y[i]/x[i])/(x[j]-x[i]);
if(a>=)continue;
b=y[i]/x[i]-a*x[i];
int t=;
for(int k=;k<=n;k++)
if(pan(a*x[k]+b,y[k]/x[k]))t+=id[k];
f[i][j]=t;
}
memset(dp,0x3f,sizeof(dp));dp[]=;
for(int i=;i<(<<n);i++)
for(int j=;j<=n;j++)
{
if(i&id[j])continue;
for(int k=j+;k<=n;k++)
dp[i|f[j][k]]=min(dp[i|f[j][k]],dp[i]+);
dp[i|id[j]]=min(dp[i|id[j]],dp[i]+);//注意处理一只小鸟只消灭一只小猪的情况!
}
printf("%d\n",dp[(<<n)-]);
}
int main()
{
for(int i=;i<=;i++)id[i]=<<(i-);
scanf("%d",&T);
while(T--)solve();
return ;
}

相关文章