20221004(匈)

20221004

题目来源:George_Plover(乔治魄罗蛙)
题目

t1 两个年轻人

思路

​ 考虑题目中所说的最优方案是什么。显然,如果只剩一堆,那么将这一堆直接选完就是最优方案。而如果剩下两堆,那么将最少的一堆选到只剩一个就是最优方案。由此,可以发现,是否必胜只与能否在只剩一堆前将其他堆选到只剩下一有关。所以,我们只需要将初始为1的堆处理一下即可。

点击查看代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,x,y) for(int i=x;i<=y;++i)
#define ll long long
using namespace std;
template<typename T>inline void in(T &x){
    x=0;int f=0;char c=getchar();
    for(;!isdigit(c);c=getchar())f|=(c=='-');
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=f?-x:x;
}
template<typename T>inline void out(T x){
    if(x<0)x=~x+1,putchar('-');
    if(x>9)out(x/10);
    putchar(x%10^48);
}
const int N=10005;
int n;
ll a[N],cnt;
int main(){
    int t;
    in(t);
    while(t--){
	in(n);
	cnt=0;
	fo(i,1,n){
	    in(a[i]);
	    if(a[i]==1)++cnt;
	}
	if(cnt==n){
	    if(cnt&1)puts("Yes");
	    else puts("No");
	}
	else if(n-cnt&1){
	    if(cnt&1)puts("No");
	    else puts("Yes");
	}
	else{
	    if(cnt&1)puts("No");
	    else puts("Yes");
	}
    }
    return 0;
}

t2 搬砖

最初思路(贪心)

​ 可以发现,每次只有两种选择。只拿一块砖或者一次拿两块砖。那我们根据一次拿两块砖的路程与各自拿一次这两块砖差值从大到小排序,计算,得出答案。当然,这样是无法过掉这道题的。

终局思路(状压记忆化搜索)

​ 因为\(n\le20\)我们显然可以想到状压,但直接状压是过不了的。而且比较麻烦,所以这里考虑记忆化搜索。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,x,y) for(int i=x;i<=y;++i)
#define ll long long
using namespace std;
template<typename T>inline void in(T &x){
    x=0;int f=0;char c=getchar();
    for(;!isdigit(c);c=getchar())f|=(c=='-');
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=f?-x:x;
}
template<typename T>inline void out(T x){
    if(x<0)x=~x+1,putchar('-');
    if(x>9)out(x/10);
    putchar(x%10^48);
}
const int N=22;
int x[N],y[N];
int n;
int eans=1e9;
int tmp[N],anx[N];
int v[N];
int dis[N][N];
int pre[(1<<23)];
inline int len(int i,int j){//求所需时间
    return pow(x[i]-x[j],2)+pow(y[i]-y[j],2);
}
inline void dfs(int pos,int com,int ans,int sta){
//当前为第pos个,已经选择了com个,当前消耗的时间,当前的选择状态
    if(ans>=eans||ans>=pre[sta])return;//答案不如之前的优就跳过
    if(v[pos]){//当前点已经被选过
	dfs(pos+1,com,ans,sta);
	return;
    }
    if(pos>n){//已经选完了所有点,统计答案
	eans=ans;
	fo(i,1,n)anx[i]=tmp[i];
	    return;
    }
    tmp[com+1]=pos,v[pos]=1,pre[sta]=ans;//tmp保存选择顺序
    dfs(pos+1,com+1,ans+2*dis[pos][0],sta+(1<<(pos-1)));//只选当前块
    fo(i,pos+1,n){//枚举选择的第二块
	if(v[i])continue;
	tmp[com+2]=i;
	v[i]=1;
	dfs(pos+1,com+2,ans+dis[0][pos]+dis[pos][i]+dis[i][0],sta+(1<<(pos-1))+(1<<(i-1)));
	//选择一次拿pos和i两块砖
	v[i]=0;
    }
    v[pos]=0;
}
int main(){
    in(x[0]),in(y[0]);
    in(n);
    fo(i,1,n)in(x[i]),in(y[i]);
	fo(i,0,n-1)
	    fo(j,i+1,n)
		dis[i][j]=dis[j][i]=len(i,j);
    memset(pre,0x7f,sizeof(pre));
    dfs(1,0,0,0);
    out(eans),putchar('\n');
    fo(i,1,n)out(anx[i]),putchar(' ');
    return 0;
}

t3 闪电链

暴力

​ 直接按照题意去构造答案。

点击查看代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,x,y) for(int i=x;i<=y;++i)
#define ll long long
using namespace std;
template<typename T>inline void in(T &x){
    x=0;int f=0;char c=getchar();
    for(;!isdigit(c);c=getchar())f|=(c=='-');
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=f?-x:x;
}
template<typename T>inline void out(T x){
    if(x<0)x=~x+1,putchar('-');
    if(x>9)out(x/10);
    putchar(x%10^48);
}
const int N=100005,mod=998244353;
int n,h;
ll ans;
int a[N],r[N];
inline void work(int x){
    r[x]=r[x-1]+a[r[x-1]];
    int kp=r[x];
    if(r[x]==n)++ans,ans%=mod;
    else if(r[x]>r[x-1]&&r[x]<n)work(x+1);
    r[x]=r[x-1]+r[x-1]-r[x-2];
    if(r[x]==kp)return;
    if(r[x]==n)++ans,ans%=mod;
    else if(r[x]>r[x-1]&&r[x]<n)work(x+1);
}
int main(){
    in(n),in(h);
    fo(i,1,n)in(a[i]);
    r[1]=1;
    r[2]=1+h;
    work(3);
    if(r[2]==1+a[1]){
	out(ans);
	return 0;
    }
    r[2]=1+a[1];
    work(3);
    out(ans);
    return 0;
}

动态规划

​ 对于条件2,我们可以将其理解为在\(A\)序列上跳跃,并且每次跳跃有两种选择:

  1. 按照现在所处位置对应的\(a_{i}\)进行跳跃。
  2. 按照上次跳跃的距离进行跳跃。

那我们显然可以想到用\(dp\)方程来进行转移。定义\(dp[i][j]\)表示当前处在\(a_{i}\)的位置,上一步跳跃了\(j\)的距离的方案数。那状态转移方程自然也就出来了,不过考虑到\(for\)循环枚举时会有很多无意义的转移,所以我们采用刷表法进行转移。

\[dp[i+a[i]][a[i]]+=dp[i][j]\\dp[i+j][j]+=dp[i][j] \]

这样就得到了一个\(O(n^{2})\)的算法,接下来再加点玄学优化即可。

能够AC的暴力

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
    x=0;int f=0;char c=getchar();
    for(;!isdigit(c);c=getchar())f|=(c=='-');
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=f?-x:x;
}
template<typename T>inline void out(T x){
    if(x<0)x=~x+1,putchar('-');
    if(x>9)out(x/10);
    putchar(x%10^48); 
}
const int N=1e5+5,mod=998244353;
int n,h,a[N];
long long dp[N];//dp[i]表示能到达i的方案数
int main(){
    in(n),in(h);
    fo(i,1,n)in(a[i]);
    dp[1]=1;
    dp[1+h]=1;
    dp[a[1]+1]=1;
    if(h!=a[h+1])//去重,避免与之后按a[i]距离跳重复
	for(int i=1+(h<<1);i<=n;i+=h){//每次按h距离不停地跳
	    ++dp[i];
	    if(a[i]==h)break;//去重,避免与之后按a[i]距离跳重复
	}
    if(h!=a[1]&&a[1]!=a[a[1]+1])//去重,避免与之后按a[i]距离跳重复
	for(int i=(a[1]<<1)|1;i<=n;i+=a[1]){//每次按a[1]距离不停地跳
	    ++dp[i];
	    if(a[i]==a[1])break;//去重,避免与之后按a[i]距离跳重复
	}
    for(int i=min(h+1,a[1]+1);i<=n;++i){//按a[i]距离不停地跳
	for(int j=i+a[i];j<=n;j+=a[i]){
	    dp[j]=((dp[j]+dp[i])%mod+mod)%mod;
	    if(a[j]==a[i])break;
	}
    }
    out(dp[n]%mod);
    return 0;
} 
posted @ 2022-10-05 08:32  リン・グァン  阅读(18)  评论(0编辑  收藏  举报