2017 NOIp提高组 DAY1 试做

感觉这年的相对上午做的那个不正经的NOIp2019好的多啊

D1T1 小凯的疑惑

题目描述

下方传送门
题目链接
上方传送门

思路分析

  • 第一眼看上去就想用 \(exgcd\) 做,但眉头一皱,这可是 \(D1T1\) ,所以果断打表
  • 然后随便抓几个小数据你就会发现满足这样的规律:\(ans = a*b-a-b\),码量堪比 A+B problem
  • 看题解有的人进行了一波证明,还是很好理解的

    1.假设这个k它真的可以被表示为 \(m*a+n*b\) (玄学的思路)
    2.在有关 \(a,b\) 的条件不变的情况下,那 \(m\)\(n\) 一定不是普通数!不然如何凑出一个原本凑不出来的数?原题中 \(m,n\) 为正整数,为打破这一规则,不妨设 \(n\) 为负整数。此时为了 \(k\) 最大,\(n=-1\)
    3.代入 \(n=-1,k=m*a-b\)。由第一种思路的第二条,\(b*a-b\) 能被 \(b\) 表示出来,为了让它不能被表示出来,又要 \(m\) 最大,所以 \(m=b-1\)
    4.代入 \(m=b-1\),原式的最大值就等于: \(a*b-a-b\)

Code

//看起来多只是因为有些东西懒得删了
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int a,b;
signed main(){
	a = read(),b = read();
	printf("%lld\n",a*b-a-b); //是的没错,就这,没了
	return 0;
}

D1T2 时间复杂度

题目描述

下方传送门
题目描述
上方传送门

思路分析

  • 一眼看上去就知道是个模拟题,非常不友好
  • 最恶心的就是这题的输入,于是从题解里得知有一种叫 sscanf 的东西,而且还学到了 scanf 的一些奇淫巧技
  • 接下来模拟循环过程即可:
    • 对于ERR的情况,很简单,就两种
      1. F和E失配
      2. 变量名冲突
    • 可以过编译的话,直接判定时间复杂度就行了,无非就是 \(O(1)\)\(O(n)\)
      1. \(O(1)\) 的话,要么两个循环变量都是常数,要么就是直接没跑,另外起止都是 \(n\) 也是,这个点很容易忽略
      2. \(O(n)\) 的话,那就是出现 \(n\) 且循环可以跑了
    • 另外就是循环之间的关系,有并列的,有嵌套的,对于嵌套的时间度才会叠加
  • 上面处理好以后用栈维护一下就行了

详见代码

Code

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define N 110
using namespace std;
int n,pro,top,pown,sbxm;//sbxm表示小明的答案,pro表示正确答案,pown表示n的指数
bool bre[N],is_n[N],CE;//bre即break,判断循环是否终止;is_n标记当前层的时间复杂度是不是O(n)
char s[N],sta[N];
int main(){
	int t;scanf("%d",&t);
	while(t--){
		scanf("%d%s",&n,s);
		if(s[2]=='1')sbxm = 0;
		else sscanf(s,"O(n^%d)",&sbxm);//sscanf用法自行百度
		pro = top = pown = 0;
		CE = 0;
		for(int i = 1;i <= n;i++){
			scanf("\n%[^\n]",s);//表示如果不遇到换行符就全部输入进去
			if(CE)continue;
			if(s[0]=='E'){
				if(!top)CE = 1;
				else if(is_n[top--])pown--;//循环终止后下面的就不能再嵌套了,所以pown--
			}else{
				sta[++top] = s[2];//循环变量都放到一起
				int len = strlen(s);
				for(int i = 1;i < top;i++)if(sta[i]==s[2])CE = 1;
				bre[top] = bre[top-1]||(s[4]=='n'&&s[len-1]!='n');
				if(s[4]!='n'&&s[len-1]!='n'){
					int a,b;
					sscanf(s,"%*s%*s%d%d",&a,&b);//*s表示过滤掉,这里相当于又读取了一遍当前行
					if(a>b)bre[top] = 1;
				}
				if(s[4]!='n'&&s[len-1]=='n')is_n[top] = 1,pown++;
				if(!bre[top])pro = max(pro,pown);
			}
		}
		if(top)CE=1;
		if(CE)puts("ERR");
		else puts(pro==sbxm?"Yes":"No");
	}
	return 0;
}

D1T3 逛公园

题目描述

下方传送门
题目链接
上方传送门

思路分析

  • 题意很明确,先求最短路,再求出路径长度在一定区间范围内的方案数。
  • 目测是个稀疏图,按理说跑 \(spfa\) 问题应该不大,但在UOJ上被卡了就跑了一遍 \(Dijkstra\)
  • 剩下就是求方案数了,然而我打了一遍暴力一直 \(MLE\) 出锅,所以考虑正解
  • 不难发现 \(K\) 值最大也就是 \(50\) ,完全可以将差值为 \(1\)~\(50\) 的路线直接统计一遍,而没必要记录所有路径长
  • 所以只需要枚举一下差值,看如果是该差值能不能从终点回到最初的起点即可。
  • 另外这题还需要判 \(0\) 环,只需要将出发状态标记一下就好,如果又回去了就说明有 \(0\)

Code

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 200010 
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,m,k,p,head[N],dis[N],ans,head2[N],f[N][66];
bool vis[N],flag[N][66];
struct edge{
	int to,next,dis;
}e[N],e2[N];
int len,len2;
void Init(){
	memset(head,0,sizeof(head));
	memset(head2,0,sizeof(head2));
	memset(f,-1,sizeof(f));
	memset(flag,0,sizeof(flag));
	len = ans = len2 = 0;
}
void addedge(int u,int v,int w){
	e[++len].to = v;
	e[len].next = head[u];
	e[len].dis = w;
	head[u] = len;
}
void add2(int u,int v,int w){
	e2[++len2].to = v;
	e2[len2].next = head2[u];
	e2[len2].dis = w;
	head2[u] = len2;
}
struct node{
	int dis,num;
	node(){}
	node(int _dis,int _num){dis=_dis,num=_num;}
	bool operator <(const node &a)const{
		return dis>a.dis;
	}
};
void Dij(int x){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	priority_queue<node>q;
	dis[x] = 0;
	q.push(node(0,x));
	while(!q.empty()){
		node p=q.top();q.pop();
		int u = p.num;
		if(vis[u])continue;
		vis[u] = 1;
		for(int i = head[u];i;i=e[i].next){
			int v = e[i].to;
			if(dis[v]>dis[u]+e[i].dis){
				dis[v] = dis[u]+e[i].dis;
				q.push(node(dis[v],v));
			}
		}
	}
}
int dfs(int u,int dc){ //跑反图求方案数
	if(dc<0||dc>k)return 0;
	if(flag[u][dc])return -1; //出现0环
	if(~f[u][dc])return f[u][dc]; //记忆化
	flag[u][dc] = 1;
	int res = 0;
	for(int i = head2[u];i;i = e2[i].next){
		int v = e2[i].to;
		int tmp = dfs(v,dc+dis[u]-dis[v]-e2[i].dis); //计算最初差值的消耗量
		if(tmp==-1)return -1;
		res = (res+tmp)%p;
	}
	if(u==1&&!dc)++res; //回到起点,且差值正好用完,说明该路径合法
	f[u][dc] = res;
	flag[u][dc] = 0;
	return res;
}
int main(){
	int t;t =read();
	while(t--){
		Init();
		n = read(),m = read(),k = read(),p = read();
		for(int i = 1;i <= m;i++){
			int a,b,c;a =read(),b = read(),c = read();
			addedge(a,b,c);
			add2(b,a,c);
		}
		Dij(1);
		bool flag = 0;
		for(int i = 0;i <= k;i++){
			int tmp = dfs(n,i);
			if(tmp==-1){
				flag = 1;
				break;
			}
			ans = (ans+tmp)%p;
		}
		if(flag)puts("-1");
		else printf("%d\n",ans);
	}
	return 0;
}

总结

  • \(T1\) 还是一如既往地找规律,像这种类型的打表最方便不过了
  • \(T2\) 竟然考了个模拟,相当的恶心
  • \(T3\) 是个最短路加 \(DP\) 混合的题,关键在于后者的状态定义和遍历的方法
  • 总体来说难度还算是比较适中的,当然想做对并不容易。正在尝试找到更多套路性的东西

另附洛谷 2017NOIp提高组题单->NOIp2017

posted @ 2020-08-16 07:04  HH_Halo  阅读(210)  评论(0编辑  收藏  举报