2020.02.19【NOIP提高组】模拟B 组8反思

我又一次被虐爆了,T4竟暴力可以过(我所认为的暴力)!
这一次只有140分,Rank20。
T1:
Coprime
题目大意
对于两个整数k 和m,如果k 和m 的最大公约数为1,则k 和m 互质。给出两个正整
数n 和m(m≤n),定义f(n,m)为1~n!中与m!互质的数的个数。其中n!=123*…*(n-1)n。
Task:给定n 和m,要求计算f(n,m)。
输入
本题设多组数据。
输入文件的第一行有一个整数T(1≤T≤100000),表示有T 组数据。
接下来有T 行,每行两个整数n 和m(2≤n≤100000,2≤m≤n)。
输出
输出文件包含T 行,每行一个整数,表示f(n,m)。
由于答案过大,所以你只要输出f(n,m) mod 131071。
131071 是M17(梅森素数,2^17-1)。
样例输入
1
3 2
样例输出
3
数据范围
对于50%的数据,T=1,2≤N≤10
对于80%的数据,T=1,2≤N≤100000
对于100%的数据,1≤T≤100000,2≤N≤100000
AcFast 友情提示:小心运算溢出,也就是RTE215 错误。。。
题解
对于一个质数p,用n!
(p-1)/p,最后就是答案,用逆元(费马小定理、欧拉函数)求出答案。(其实是道板子题)

#include<bits/stdc++.h>
#define RE register
#define ll long long
using namespace std;
const int P=131071;
int prime[100001],num=0,T;
ll fact[100001],ans,phi[100001];
bool bz[100001];
inline int read() {
	int s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s;
}
inline ll power(ll a,ll b) {
	ll result=1;
	while(b>0) {
		if(b&1)result=result*a%P;
		a=a*a%P;
		b>>=1;
	}
	return result;
}
inline void init() {
	fact[1]=1;
	for(RE int i=2; i<=100000; i++)fact[i]=fact[i-1]*i%P;
	phi[1]=1;
	for(RE int i=2; i<=100000; i++) {
		phi[i]=phi[i-1];
		if(!bz[i]) {
			phi[i]=phi[i]*(i-1)%P*power(i,P-2)%P;
			prime[++num]=i;
		}
		for(RE int j=1; j<=num; j++) {
			if(prime[j]*i>100000)break;
			bz[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
signed int main() {
	freopen("coprime.in","r",stdin);
	freopen("coprime.out","w",stdout);
	init();
	T=read();
	while(T--) {
		int n=read(),m=read();
		printf("%lld\n",phi[m]*fact[n]%P);
	}
}

T2:
Matrix
题目大意
给你一个N*M 的矩阵,矩阵里面的元素要么是正数,要么是负数,它们的绝对值不大
于10000。现在你可以对矩阵进行两种操作:
1、将某一列的元素全部取反。
2、将某一行的元素全部取反。
你可以执行任意次操作。
Task:通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操
作次数,否则输出“impossible”(不包括双引号)。
输入
输入文件的第一行有两个整数n 和m(1≤n,m≤1000),表示矩阵的大小。
接下来有N 行,每行M 个数,每个数之间有一个空格。
输出
通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操作次
数,否则输出“impossible”(不包括双引号)。
样例输入
2 4
3 7 -6 1
-2 -6 8 -4
样例输出
2
数据范围
对于40%的数据,1≤N,M≤10
对于100%的数据,2≤N,M≤1000
题解
很明显,给出的那几个数只需要判断正负,所以我们先把它转化为01矩阵,正数为0,负数为1
那么样例就变成了这个样子

1 1 0 1
0 0 1 0

不难发现,先操作哪一行哪一列的先后顺序并不影响最终的状态,只要操作的数量相同,操作的行列相同就可以了。所以我们现在就有一个思路,我们可以先尝试把每一行都转化为相同状态,然后再把每一列进行处理,就可以得出答案。那么,每一行,它至多有两种状态,一种是操作,另一种是不操作,这样我们就可以以第一行为基准,看一下之后的每一行是否能够变成第一行的一二种状态,然后再求最小值。
Code

#include<cstdio>
using namespace std;
int n,m,s[1001][1001],s2[1001][1001],ans=0,ans2=1,sum=0x3f3f3f3f;
bool bz=0,bz2=0;
inline int min(int a,int b) {
	if(a<b)return a;
	return b;
}
int main() {
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			int x;
			scanf("%d",&x);
			if(x>0)s[i][j]=0;
			else s[i][j]=1;
			s2[i][j]=(s[i][j]+1)&1;
		}
	for(int i=2;i<=n;i++) {
		bool check=0,check2=0,bz3=0,bz4=0;
		for(int j=1;j<=m;j++) {
			if(s[i][j]!=s[1][j])check=1;
			if(s[i][j]!=s2[1][j])check2=1;
			if(s2[i][j]!=s[1][j])bz3=1;
			if(s2[i][j]!=s2[1][j])bz4=1;
			if(check&&check2&&bz3&&bz4)break;
		}
		if(check&&check2)bz=1;
		if(bz3&&bz4)bz2=1;
		if(bz&&bz2)break;
		if(!bz3)ans++;
		if(!bz4)ans2++;
	}
	if(!bz) {
		for(int j=1;j<=m;j++)
		ans+=s[1][j];
		sum=ans;
	}
	if(!bz2) {
		for(int j=1;j<=m;j++)
		ans2+=s2[1][j];
		sum=min(sum,ans2);
	}
	if(bz&&bz2)puts("impossible");
	else printf("%d",sum);
}

T3:
题目大意
Hanoi
Mpq 小时候只玩过俄罗斯方块这个经典的小游戏,当时他还不知道Hanoi 究竟是
什么东西。话说当Mpq 第一次认识Hanoi 是在初三那年的联赛。由于Mpq 之前并不知
道Hanoi 是什么东西,所以那一年他做完前三题之后很郁闷地坐了1 个半小时。。。
好了,现在Mpq 成长了,他已经解决当年联赛那道Hanoi 了,在前几个月,他又
发现一道关于Hanoi 的题目了,很幸运的是这个题目他知道怎么做了。。。然后为了让大
家体验一下Mpq 初三联赛那种无奈的感觉,所以,这道题就神奇地出现在你们眼前。
Task:赶快AC 这道题目,然后你就可以狂鄙视,甚至是无视Mpq 的存在了!!!
哎,吹着吹着发现我还没把题目写下来。。。。
现在给你M 根柱子,初始的时候有N 个大小不一样的盘插在第一根柱子上面。同
样地,规格大的盘子不能放在规格比它小的盘子上面。问最少需要多少次的移动才能将
这N 个盘从第一根柱子移动到最后一根柱子上面?
输入
输入文件的第一行有两个整数n,m(1≤n≤100000,3≤m≤10),分别表示有n 个盘子和m
根柱子。
输出
输出文件只有一行,一个整数,表示最少的移动次数。保证这个移动次数不会超过
2^63-1。
样例输入
4 3
样例输出
15
数据范围
对于30%的数据,M=3
对于80%的数据,1≤N≤100,3≤M≤10
对于100%的数据,1≤N≤100000,6≤M≤10
题解
看到这道题时,知道m才这么小,一下就想到了状压,一股脑往状压去想了,结果想对了一半,是dp,但不是状压。说这么多无聊的,应该开始讲正解了。

设一个状态f[i][j]表示i个盘子,j根柱子的最小步数。

可是还是想不到怎么转移,那就先从最简单的三根柱子开始。
i个盘子三根柱子的答案套用公式:

f[i][3]=f[i-1][3]*2+1;

那么对于当前的一个k,代表从第一个柱子搬k个下来,然后再把他们给堆在一起放在最后一个柱子,那么

f[i][j]=f[k][j]*2+f[i-k][j-1];

这么一下来,答案是对了,但是超时了。
所以我们就用一个g[i][j]代表一个最优的k,每一次k就等于g[i-1][j],和g[i-1][j]+1,每次就从上面选最优转移。

Code

#include<cstdio>
using namespace std;
int n,m;
long long f[100001][11],g[100001][11];
int main() {
	freopen("hanoi.in","r",stdin);
	freopen("hanoi.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=63;i++) {
		f[i][3]=(f[i-1][3]*2)|1;
		g[i][3]=i-1;
	}
	for(int i=1;i<=n;i++)
		for(int j=4;j<=m;j++) {
			int k=g[i-1][j];
			f[i][j]=(f[k][j]<<1)+f[i-k][j-1];
			g[i][j]=k;
			k++;
			if((f[k][j]<<1)+f[i-k][j-1]<f[i][j]) {
				f[i][j]=(f[k][j]<<1)+f[i-k][j-1];
				g[i][j]=k;
			}
		}
	printf("%lld",f[n][m]);
}

T4
题目大意
Road
Z 国是一个拥有N 个岛的国家。这N 个岛用N-1 条桥来连接,且任意两个岛之间都可以互达。
某商人听说Z 国是一个很富有的国家,所以他想到Z 国闯一闯。经过他仔细的观察,他发现某样商品特别受欢迎,而且由于各岛之间沟通联系不够多,所以这样物品在每个岛的价格可能都不同。
Task:商人开始在编号为x 的岛上,然后他要走到编号为y 的岛上。在这期间,他可以在x 岛y 的路径上买一件商品,和卖一件商品。,注意,仅能买一件和卖一件!显然你要计算商人从岛x 到岛y 最多能赚多少钱。
输入
输入第一行有一个整数N(1≤n≤50000),表示Z 国有N 个岛。
接下来有N 行,每行一个整数Ci(1≤Ci≤50000),第N+i 行的Ci 表示商品在岛i 的价
格。
再接下来有N-1 行,每行两个整数x,y(1≤x,y≤50000),表示岛x 和岛y 之间有一条
桥。
接下来有一个整数M,表示有M 个询问。
然后M 行,每行两个整数,x,y(1≤x,y≤50000),表示询问你,商人从岛x 到岛y 最多
能赚多少钱?
输出
对于每次询问,如果商人能赚到钱,则输出最多能赚多少钱。
如果不能赚钱,就输出0(你可以这样理解——亏本生意谁都不会做_
样例输入
4
1
2
3
4
1 2
1 4
2 3
3
1 3
3 1
1 4
样例输出
2
0
3
数据范围
数据约定:
对于30%的数据, 1≤N,M≤100
对于60%的数据,1≤N,M≤1000
对于100%的数据,1≤N,M≤50000
题解
好像梁正昊给我们讲过这题
这道题令我特别的愤怒,竟是一道暴力!本O(nm)所不能过的题让某些人水了过去,就连LCA直接打一个个往上跳的打暴力都能过。严厉谴责出数据人!
就是一个暴力,找到LCA,(劝各位打高级的LCA算法)然后把y到LCA这条路径上的点加入数列,然后x到lca的路径找答案,再逆向在数列里找答案,一道暴力就AC了。
Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,a[50001],head[50001],cnt=0,st[50001][21],dep[50001],m,maxn,minn,minn2,ans,b[50001],num;
struct node {
	int to,next;
} edge[100000];
void add(int x,int y) {
	edge[cnt].to=y;
	edge[cnt].next=head[x];
	head[x]=cnt++;
}
void dfs(int now) {
	for(int i=head[now]; i!=-1; i=edge[i].next) {
		int son=edge[i].to;
		if(son==st[now][0])continue;
		st[son][0]=now;
		dep[son]=dep[now]+1;
		dfs(son);
	}
}
int LCA(int x,int y) {
	if(dep[x]<dep[y])swap(x,y);
	if(dep[x]>dep[y]) {
		for(int j=log2(dep[x]-dep[y]); j>=0; j--)
			if(dep[st[x][j]]>dep[y])x=st[x][j];
		x=st[x][0];
		if(x==y)return x;
	}
	for(int j=log2(dep[x]); j>=0; j--)
		if(st[x][j]!=st[y][j])
			x=st[x][j],y=st[y][j];
	return st[x][0];
}
int main() {
	/*freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);*/
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	memset(head,-1,sizeof(head));
	for(int i=1; i<n; i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	st[1][0]=1;
	dfs(1);
	for(int j=1; j<=log2(n); j++)
		for(int i=1; i<=n; i++)
			st[i][j]=st[st[i][j-1]][j-1];
	scanf("%d",&m);
	while(m--) {
		int u,v;
		scanf("%d%d",&u,&v);
		int lca=LCA(u,v);
		ans=0;
		minn=0x3f3f3f3f;
		num=0;
		maxn=0;
		while(u!=lca) {
			ans=max(ans,a[u]-minn);
			minn=min(minn,a[u]);
			u=st[u][0];
		}
		ans=max(ans,a[u]-minn);
		minn=min(minn,a[u]);
		while(v!=lca) {
			b[++num]=v;
			v=st[v][0];
		}
		b[++num]=v;
		minn2=minn;
		for(int i=num; i>=1; i--) {
			maxn=max(maxn,a[b[i]]);
			ans=max(ans,a[b[i]]-minn);
			minn=min(minn,a[b[i]]);
		}
		ans=max(ans,maxn-minn2);
		printf("%d\n",ans);
	}
}

请各位大佬多多知道我这个蒟蒻。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值