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解法:

首先可以按照𝑝 ) 从大到排序,这样每个点能走到的位置就变成了一段连续的后缀,我们直接在新
序列上做背包,用𝑓[𝑖][𝑗]表示到达第𝑖个位置,当前耐久度为𝑗是否可行
转移的时候分两种情况讨论:

  1. 到达该点
  2. 不到达该点
    对𝑛位置特殊判断一下,最后枚举𝑛所在位置𝑓数组的第二维,判断一下即可
    注意一个小细节: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 ;
}

posted @ 2019-02-15 14:22  fishers  阅读(476)  评论(0编辑  收藏  举报