noip 真题班刷题记录及总结思考

进度一览

2019牛客暑期NOIP真题班提高组7-图论

2019牛客暑期NOIP真题班普及组3-枚举1

2019牛客暑期NOIP真题班提高组6-树

2019牛客暑期NOIP真题班提高组5-DFS

2019牛客暑期NOIP真题班普及组2-模拟2

2019牛客暑期NOIP真题班提高组4-贪心

2019牛客暑期NOIP真题班提高组3-数学

2019牛客暑期NOIP真题班普及组1-模拟1

2019牛客暑期NOIP真题班提高组2-二分

2019牛客暑期NOIP真题班提高组1-模拟

2019牛客暑期NOIP真题班普及组10-DP2

2019牛客暑期NOIP真题班普及组9-DP1

2019牛客暑期NOIP真题班普及组8-贪心

2019牛客暑期NOIP真题班普及组7-数学

2019牛客暑期NOIP真题班普及组6-排序

2019牛客暑期NOIP真题班普及组5-字符串处理

2019牛客暑期NOIP真题班提高组10-DP3

2019牛客暑期NOIP真题班提高组9-DP2

2019牛客暑期NOIP真题班普及组4-枚举2

2019牛客暑期NOIP真题班提高组8-DP1

听课目标:

  • 训练自己的专注程度,2x要求跟上思路,注意力高度集中
  • 我并不希望这么好的参考题你是按照yxc的步伐一步一步做出来的,首先自己读题,自己思考,自己打代码。如果遇到瓶颈再看他的思路,然后自己写代码,如果实现起来实在有问题,标注该算法,然后课下自己搞。
  • 检验这堂课是否有成效的标准是:此题的整个思维模型,数据范围及算法的推导,代码实现,在考场上我能够完全思考到位嘛

9.28 PJ DP1

数字游戏

prework:

  • 环形DP->区间DP(数组开大两倍)

  • 数学意义下的取模需要变负为正再取模

  • 状态表示:f[L][R][i]LR的区间选了i组的乘积最大值,同样的,`g[L][R][i]`LR的区间选了i组的乘积最小值

    • 集合:
  • 状态转移:

首先考虑f[l][r][i]如何计算,关键是寻找“集合划分的依据”,划分依据一般选取“最后一步的操作”,所以这里我们可以按最后一部分的位置来将f[l][r][i]所表示的所有方案划分成若干类:

最后一段是sum[l+i-1...r]的所有划分方案;

最后一段是sum[l+i...r]的所有划分方案;

...

最后一段是sum[r...r]的所有划分方案;

如果最后一段是sum[k...r],

那么这一类的最大值是 f[l][k-1][i-1]*sum[k~r],其中sum()计算某一段数的和模10的余数。

最终枚举所有长度是n的区间,取最大值/最小值即可。

//2019/09/28
const int N=110,M=10;

int f[N][N][M],g[N][N][M];
int a[N],sum[N];
int n,m;

inline int get_sum(int l,int r){
	int res=sum[r]-sum[l-1];
	while(res<0)res+=10;
	return res%10;
}

int main(){
	rd(n),rd(m);
	rep(i,1,n){
		rd(a[i]);
		a[i+n]=a[i];
	}
	rep(i,1,2*n)sum[i]=sum[i-1]+a[i];
	rep(i,1,m)
		rep(len,i,n)
			rep(l,1,n){
				int r=l+len-1;
				if(i==1)f[l][r][i]=g[l][r][i]=get_sum(l,r);
				else{
					f[l][r][i]=INT_MIN;
					g[l][r][i]=INT_MAX;
					rep(k,l+i-1,r){
						int t=get_sum(k,r);
						f[l][r][i]=max(f[l][r][i],f[l][k-1][i-1]*t);
						g[l][r][i]=min(g[l][r][i],g[l][k-1][i-1]*t);
					}
				}
			}
	int maxx=INT_MIN,minn=INT_MAX;
	rep(i,1,n){
		maxx=max(maxx,f[i][i+n-1][m]);
		minn=min(minn,g[i][i+n-1][m]);
	}
	printf("%d\n%d\n",minn,maxx);
	return 0;
}

开心的金明

01背包裸题;

  • 总钱数相当于背包总容量;
  • 每件物品的价格相当于体积;
  • 每件物品的价格乘以重要度相当于价值;

守望者的逃离

首先比较跑步和放技能哪个更快:

  • 跑步:平均一秒 1717 米
  • 放技能:平均2.5秒恢复10点魔法,再加一秒闪烁时间,一共是3.5秒可以移动60米, 所以平均每秒 17又1/7米

所以放技能稍快一些。

因此当我们有充足的放技能时间时,一定要放技能,所以只有最后一小段没时间放技能的时候,才会用跑步的方式。

求解:

f[i]表示用i的时间,最多可以跑多远。

先求出只用闪烁技能时,每秒最多可以跑多远。

再用跑步的方式来“插缝”,递推出结合两种方式时,每秒最多可以跑多远。

//2019/09/28
const int N=300010;
int B,S,T;
int f[N];

int main(){
	rd(B),rd(S),rd(T);//B:蓝,S:需要逃跑的距离,T:仅剩的时间 
	rep(i,1,T){
		if(B>=10){
			B-=10;//掉蓝
			f[i]=f[i-1]+60;
		}
		else {
			B+=4;//回蓝,这时站住不动
			f[i]=f[i-1];
		}
	}
	rep(i,1,T){
		f[i]=max(f[i],f[i-1]+17);
	}
	if(f[T]>=S){
		rep(i,1,T){
			if(f[i]>=S){
				puts("Yes");
				printf("%d",i);
				break;
			}
		}
	}
	else{
		puts("No");
		printf("%d",f[T]);
	}
	return 0;
}

子矩阵咕咕咕

PJ5 字符串处理

乒乓球

#include<bits/stdc++.h>
using namespace std;

inline void work(string s,int score){
	int a=0,b=0;
	for(int i=0;i<s.size() && s[i]!='E';++i){
		if(s[i]=='W')a++;
		else b++;
		if(max(a,b)>=score && abs(a-b)>=2){///////////////////////////// 
			printf("%d:%d\n",a,b);
			a=b=0;
		}
	}
	printf("%d:%d\n",a,b);
}

int main(){
	string str,line;
	while(cin>>line)str+=line;
	work(str,11);
	puts("");
	work(str,21);
	return 0;
}

ISBN 号码

#include<bits/stdc++.h>
using namespace std;
char ss[1<<21],*p1=ss,*p2=ss;
inline char gc(){return p1==p2 && (p2=(p1=ss)+fread(ss,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
template <typename T>inline void rd(T &x){x=0;char c=gc();int f=0;while(!isdigit(c)){f|=c=='-';c=gc();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}x=f?-x:x;}
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define ee(i,u) for(int i=head[u];i;i=e[i].next)

int wei,ans;
int main(){
	#ifdef WIN32
	freopen("isbn.txt","r",stdin);
	#endif
	string s;
	cin>>s;
	for(int i=0;i+1<s.size();++i){
		if(s[i]=='-')continue;
		wei++;
		if(s[i]=='X')ans+=wei*10;
		else ans+=wei*(s[i]-'0');
	}
	ans%=11;
	char t='X';
	if(ans<10)t=ans+'0';
	if(s.back()==t)puts("Right");
	else {
		s.back()=t;
		cout<<s;
	}
	return 0;
}

多项式输出

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n;cin>>n;
	for(int i=n;i>=0;--i){
		int a;scanf("%d",&a);
		if(a==0)continue;
		
		if(i<n && a>0)printf("+");//不是首项 
		else if(a<0)printf("-");
		
		a=a<0?-a:a;
		
		if(a>1 || i==0)printf("%d",a);//系数>1 || 是常数项 才输出 
		if(i>0)printf("x");//不是常数项都输出x 
		if(a>0 && i>1)printf("^%d",i);//不是末尾两项 && 系数不为0 
	}
	return 0;
} 

PJ DP3 10.22

传球游戏

算法:DP,环形DP

复杂度 O(N* M)

SOL:

不妨设小蛮在0号,所有人的编号是0∼n−1。

状态表示 f[i, j]

  • 集合:第j次传球后球在第i号同学手里的方案数
  • 属性:集合中元素的数量;

状态计算:

  • f[i, j]
    

    所表示的集合可以划分成两个子集:

    • 从左边传过来的集合大小是f[i-1,j-1]
    • 从右边传过来的集合大小是f[i+1,j+1]
  • f[i,j]等于两个子集的元素数之和。

注意当i==0j==n-1时需要特殊处理边界(因为是环状的)。

最终答案就是f[0][m]

const int N=35;
int f[N][N];//第j次传球后球在第i号同学手里的方案数
int n,m;

int main(){
    #ifdef WIN32
    freopen("a.txt","r",stdin);
    #endif
    rd(n),rd(m);
    f[0][0]=1;//设小蛮是第0号
    rep(j,1,m)
        rep(i,0,n-1)
            f[i][j]=f[(i-1+n)%n][j-1]+f[(i+1)%n][j-1];
    printf("%d",f[0][m]);
    return 0;
}

摆花

算法知识点:DP,多重背包问题,背包问题求方案数

复杂度:O(\(n^{2}a\))

问题转化:

  • 将花盆数量看作背包容量;
  • 将花看作物品,体积是1,第 i 种物品最多选 \(a_i\) 个;
  • 问题:将背包装满的方案数是多少?

这是典型的多重背包求方案数问题:

  • 状态表示:f[i,j] 表示前i个物品,总体积是j的方案数;
  • 状态计算:f[i,j]=f[i-1,j]+f[i-1,j-1]+...+f[i-1,j-a[i]]
const int N=110,mod=1000007;
int f[N],a[N];
int n,m;

int main(){
    rd(n),rd(m);
    rep(i,1,n)rd(a[i]);
    f[0]=1;
    rep(i,1,n){
        dwn(j,m,0)
            rep(k,1,min(a[i],j))
                f[j]=(f[j]+f[j-k])%mod;
    }
    printf("%d\n",f[m]);
    return 0;
}
posted @ 2019-10-08 11:50  设计涉及社稷  阅读(265)  评论(0编辑  收藏  举报