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
的情况,很简单,就两种- F和E失配
- 变量名冲突
- 可以过编译的话,直接判定时间复杂度就行了,无非就是 \(O(1)\) 和 \(O(n)\)
- \(O(1)\) 的话,要么两个循环变量都是常数,要么就是直接没跑,另外起止都是 \(n\) 也是,这个点很容易忽略
- \(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