2019年牛客寒假算法基础集训营1
2019年牛客寒假算法基础集训营1
A-模拟
倒序模拟
https://ac.nowcoder.com/acm/contest/317/A
代码:
//#include<bits/stdc++.h>
//using namespace std;
#include<stdio.h>
typedef long long ll;
int n;
ll X;
ll ans = 0;
struct ve{
int opt;
ll x;
};
struct ve V[110];
int main(){
scanf("%d%ld",&n,&X);
ans = (ll)X;
for(int i=n;i>=1;i--){
int opt;
ll x;
scanf("%d%ld",&opt,&x);
V[i].opt = opt;
V[i].x = x;
}
for(int i=1;i<=n;i++){
if(V[i].opt == 1) ans -= V[i].x;
if(V[i].opt == 2) ans += V[i].x;
if(V[i].opt == 3) ans /= V[i].x;
if(V[i].opt == 4) ans *= V[i].x;
}
printf("%lld",ans);
return 0;
}
B贪心+构造(一大一小)
https://ac.nowcoder.com/acm/contest/317/B
1.输入的序列其实用处不大,因为最终不需要输出方案,我们只需要记录下2/0/4分别出现的次数即可
一个显然的构造策略是首先放置4, 0, 4, 0,直到其中一个用光。
接下来如果4多余,那么可以按4,0,4,0,…,4,2,4,2,…(先4后2)的方法构造
如果0多余,可以按照4,0,4,0 … 4,0,2,0,2 …(先2后0)的方法构造
std中的a数组展示了其中一种最优的构造方案
2.实际上此题还可以推广到更一般的情况,也就是第一个位置放最大的,第二个位置放最小的,第三个位置放
第二大的以此类推,这种思路写起来也会更简单一些
int a[N];
int main(){
int n;
LL sum=0;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a,a+n+1);
for(int i=n;i>n/2;i--)
sum+=pow(a[i]-a[n-i],2)+pow(a[n-i+1]-a[i],2); //一大一小
cout<<sum<<endl;
return 0;
}
C-背包dp、线性基
https://ac.nowcoder.com/acm/contest/317/C
1.背包dp解法:
首先可以按照𝑝 ) 从大到排序,这样每个点能走到的位置就变成了一段连续的后缀,我们直接在新
序列上做背包,用𝑓[𝑖][𝑗]表示到达第𝑖个位置,当前耐久度为𝑗是否可行
转移的时候分两种情况讨论:
- 到达该点
- 不到达该点
对𝑛位置特殊判断一下,最后枚举𝑛所在位置𝑓数组的第二维,判断一下即可
注意一个小细节:dp数组的第二维不能只开到3000
时间复杂度:𝑂(𝑛𝑝)
#include <bits/stdc++.h>
using namespace std;
bool f[3010][10010];
int n;
struct Node {
int id, val;
}a[3010];
bool cmp(Node a, Node b) { return a.val > b.val; } //排序自定义比较函数
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i].val), a[i].id = i;//输入数据
sort(a + 1, a + n + 1, cmp); //按从大到小排序
int l;
for(int i = 1; i <= n; ++i)
//找到起点
if(a[i].id == 1) {
l = i;
f[i][a[i].val] = 1; //起点设置为可行解
break;
}
for(int i = l + 1; i <= n; ++i) {
//如果到了终点
if(a[i].id == n) {
l = i;
for(int j = 6010; j >= 0; --j) f[i][j] |= f[i - 1][j ^ a[i].val];
break;
}
if(a[i].val == a[i - 1].val) {
for(int j = 0; j <= 6010; ++j) f[i][j] |= f[i - 1][j];//不是到终点 如果和前一个点的值相同,说明这个点和上一个可行性一样
} else {
for(int j = 0; j <= 6010; ++j) f[i][j] |= f[i - 1][j], f[i][j] |= f[i - 1][j ^ a[i].val];//如果没到终点 和前一个点也不一样 转成背包问题推导
}
}
for(int i = 6010; i; --i)
if(f[l][i]) return printf("%d\n", i), 0;
puts("-1");
}
2.线性基做法
筛选数据,合法的数据值是小于起点的 大于终点的,再用线性基
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e3+5;
int A[maxn];
int B[maxn];
int P[maxn];
int cnt;
void solve() {
for (int i = 1; i <= cnt; ++i) {
for (int j = 15; j >= 0; --j) {
if ((A[i]>>j) & 1) {
if (P[j] == 0) {
P[j] = A[i];
break;
} else {
A[i] ^= P[j];
}
}
}
}
}
int main() {
std::ios::sync_with_stdio(false);
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> B[i];
}
for (int i = 2; i < n; ++i) {
if (B[i] < B[1] && B[i] > B[n]) {
A[++cnt] = B[i];
}
}
solve(); //构造线性基
int ans = B[1]^B[n];
//如果可以使得ans更大,就把这一位的基xor进ans。
for (int j = 15; j >= 0; --j) {
ans = max(ans, ans^P[j]); //从线性基中得到最大值,这里是选出满足条件:下一步能量值大于前一步能力 的点
}
if (ans > 0 && B[1] > B[n])
cout << ans << endl;
else
cout << -1 << endl;
return 0;
}
3.bfs做法:
#include <bits/stdc++.h>
using namespace std;
int n,ans;
bool vis[5005];
int pre[3005];
void bfs(){
queue<int> q; //初始化定义队列
memset(vis, false, sizeof(vis)); //对各个点初始化为未访问
q.push(pre[1]); //起点入队
vis[pre[1]] = true; //起点标记未用过
while(!q.empty()){
int now = q.front(); //拿出队头的能量值
q.pop(); //对头出队
for(int i=2;i<=n;i++){
//如果队头的能量值 比其他的大 说明能到达这个新的点
if(now > pre[i]){
int nex = (now ^ pre[i]); //求异或和
if(i == n) ans = max(ans, nex); //如果到了终点 取最大值
if(!vis[nex] && i < n){ //如果没有出现过nex这个异或和 就加入队列
vis[nex] = true;
q.push(nex);
}
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&pre[i]); //输入数据
}
ans = 0; //初始化值为0
bfs(); //开始搜索
printf("%d\n", ans > 0 ? ans : -1);
return 0;
}
蓝桥杯刷习惯了就只会暴搜了。。
dfs暴力搜索 过10%数据
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int a[3010];
int vst[3010];
ll ans = -1;
int flag = 0;
void dfs(int cur,int last,ll t){
if(cur == n-1){
if(a[last] > a[cur] || last == n-1){
ans = max(ans,t^a[last]);
flag = 1;
}
return;
}
for(int i=0;i<n;i++){
if(!vst[i]){
if(a[last] <= a[i]) continue;
vst[i] = 1;
ll dd = t;
t ^= a[i];
dfs(i,cur,t);
t = dd;
vst[i] = 0;
}
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
ans = a[0];
vst[0] = 1;
dfs(0,0,ans); //0 0边界需要调整
// printf("%lld",ans);
if(flag || ans != a[0] || n==1|| ans ==0) printf("%lld",ans);
else printf("-1");
return 0;
}
D-欧拉函数,gcd,快速幂
欧拉函数:
根据性质:若gcd (𝑛,𝑥) = 1,那么gcd (𝑛,𝑛 − 𝑥)一定等于1
可知与n互质的数一定是成对出现的,而且两人从两端走所以x和n-x一定同时走到
每对互质的数一共会给两人分别带来kn的收益。也就是说每个人的总收益是kϕ(n)2∗n(与n互质的数的和为ϕ(n)2∗n),于是直接这样算出来这个值然后乘A+B就行了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
//欧拉函数:求出小于等于n的 与n互质的个数
ll Euler(long long n) {
ll res = n;
for(int i = 2; i*i <=n; i++){
if(n%i == 0) {
res -= res/i;
while(n%i == 0)
n /= i;
}
}
if(n>1)
return res -= res/n;
return res;
}
//快速幂
ll pow_mod(ll a, ll b){//a的b次方
if(b == 0) return 1%mod;
ll ret = pow_mod(a, b/2);
ret = ret * ret % mod;
if(b % 2 == 1) ret = ret * a % mod;
return ret;
}
int main() {
std::ios::sync_with_stdio(false);
ll n, k, A, B;
cin >> n >> k >> A >> B;
cout << ((A+B)*pow_mod(k, Euler(n)/2*n)) % mod << endl;
return 0;
}
暴力做法超时过80%数据
不用欧拉函数性质的超时代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a%b);
}
ll pow_mod(ll a, ll b){//a的b次方
if(b == 0) return 1%MOD;
ll ret = pow_mod(a, b/2);
ret = ret * ret % MOD;
if(b % 2 == 1) ret = ret * a % MOD;
return ret;
}
int n,k;
ll a,b;
ll ans = 0;
int main(){
cin>>n>>k>>a>>b;
for(int i=1,j=n-1;i<=n-1,j>=1;i++,j--){
if(gcd(n,i)==1 && gcd(n,j)==1){
a = a * pow_mod(k,i)%MOD;
b = b * pow_mod(k,j)%MOD;
}
}
printf("%lld",(a+b)%MOD);
return 0;
}
E-差分数组
题目链接:https://ac.nowcoder.com/acm/contest/317/E
思路
这题考查的是对差分数组原理和前缀和的理解。
四个数组分别记录朝着四个方向下放的个数最后求个前缀,就代表着这一行中从这个点开始作为起点的轰炸区域个数,四个数组分别向着四个方向下放最终得到的四个数组分别是前。
4个数组对应下面4条箭头:
图中的绿色是要进行区间操作的菱形。红色圆圈是打的+1操作,蓝色圆圈是打的-1操作。它们是成对出现的,每个红色圆圈都有一个蓝色圆圈来消除它。 它们的标记传递方向是那个紫色的箭头。所以有四种传递方向。就要有四个标记数组。这样传递的话就可以按行遍历,边遍历边下传。
在矩形中,我们在四个角上进行++--,然后利用差分的性质,就解决了区间更新,因为矩形的差分是横着或者竖着的,最后的求和非常容易,但是这里不一样。最后看了题解豁然大悟,原来差分还可以动态的来,本行的差分数组使用完了,还可以把差分数组下传,继续在下一层继续起到作用。
由于可能出现越界的情况,但是标记还是需要处理的。所以就要加个偏移量来进行处理,但是最后计算,只是算原来的矩形。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define ll long long
#define pb push_back
#define pm make_pair
#define fi first
#define se second
using namespace std;
const int MAX = 3030,ADD = 1000; //ADD表示偏移量 防止越界
int a[MAX][MAX],b[MAX][MAX],c[MAX][MAX],d[MAX][MAX]; //abcd4个数组分别表示4个方向
//上半部分
void up(int x,int y,int l) {
a[x-l/2][y]++,b[x-l/2][y+1]--;
a[x+1][y-l/2-1]--,b[x+1][y+l/2+2]++;
}
//下半部分
void down(int x,int y,int l){
c[x+1][y-l/2+1]++,d[x+1][y+l/2]--;
c[x+l/2+1][y+1]--,d[x+l/2+1][y]++;
}
int main() {
int n,m,q;
cin>>n>>m>>q;
for(int op,x,y,l,i = 1; i<=q; i++) {
scanf("%d%d%d%d",&op,&x,&y,&l);
x+=ADD,y+=ADD; //加上偏移量 防止越界
if(op == 1) up(x,y,l),down(x,y,l);
if(op == 2) up(x,y,l);
}
//差分数组 差分传递
for(int i = 1; i<=n+ADD*2; i++) {
for(int j = 1; j<=m+ADD*2; j++) {
a[i][j] += a[i-1][j+1];
b[i][j] += b[i-1][j-1];
c[i][j] += c[i-1][j-1];
d[i][j] += d[i-1][j+1];
}
}
//计算异或和
int ans = 0;
for(int i = 1; i<=n+ADD*2; i++) {
int tmp = 0;
for(int j = 1; j<=m+ADD*2; j++) {
tmp += a[i][j] + b[i][j] + c[i][j] + d[i][j];
if(i>=ADD+1 && i<=ADD+n && j>=ADD+1 && j<=ADD+m) ans ^=tmp;
}
}
cout << ans <<endl;
return 0 ;
}