7.16集训总结

T1-取餐码

 

找到n以内的质数,并求出第m个质数,显然就是求质数的板子题

5e6的数据范围,暴力打法肯定会被卡,所以采用埃氏筛或欧拉筛。相比来说埃氏筛更好理解,但还是要对欧拉筛的模板牢牢掌握,防止数据更大时被卡。

代码如下(欧拉筛模板):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll qr()
{
	ll x=0,f=0;char ch=0;
	while(!isdigit(ch)) {f|=ch=='-';ch=getchar();}
	while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return f? -x:x;
}
const ll maxn=1e7;
ll n,m;
ll prime[maxn];
ll tot;
ll v[maxn];
void init()
{
	n=qr();m=qr();	
} 
void get_prime()
{
	prime[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(v[i]==0)
		{
			v[i]=i;
			prime[++tot]=i;
		}
		for(int j=1;j<=tot;j++)
		{
			if(prime[j]>v[i]||prime[j]>n/i) break;
			v[i*prime[j]]=prime[j];
		}
	}
}
void print()
{
	cout<<tot<<' '<<prime[m];
}
int main()
{
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	init();
	get_prime();
	print();
}


T2-堆人塔

 对这个排列不断分段,很明显可以用dfs模拟分层的这个过程,三个参数分别为左端点、右端点以及当前的层数。但dfs的时间复杂度为O(n^{2}),只能拿到前40分。

再次寻找dfs的缺陷,我们发现每次分段后都用了一层循环寻找区间最大值,于是优化思路就是快速找到区间最大值。

求区间最大值的方法可以用ST表进行构造,以O(log n )的复杂度先构造好ST表后,查找的时间复杂度就是O(1),这样整体的时间复杂度就为O(n*log n)

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll qr()
{
	ll x=0,f=0;char ch=0;
	while(!isdigit(ch)) {f|=ch=='-';ch=getchar();}
	while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return f? -x:x;
}
ll n;
struct fun
{
	ll num,pla,flo;
}per[1000000];
ll pos[1000000];
ll st[1000000][30];
void init()
{
	n=qr();
	for(int i=1;i<=n;i++)
	{
		per[i].num=qr();
		pos[per[i].num]=i;
	}
}
void sol()
{
	for(int i=1;i<=n;i++)
	{
		st[i][0]=per[i].num;
	}
	int LOG=log2(n);
	for(int j=1;j<=LOG;j++)
	{
		for(int i=1;i<=n-(1<<j)+1;i++)
		{
			st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);	
		} 
	}
}
ll ask(ll x,ll y)
{
	int s=log2(y-x+1);
	return max(st[x][s],st[y-(1<<s)+1][s]);
}
void dfs(ll lef,ll rig,ll F)
{
	if(lef>rig) return ;
	if(lef==rig) 
	{
		per[lef].flo=F;
		return ;
	}
	int pp=ask(lef,rig);
	per[pos[pp]].flo=F;
	dfs(lef,pos[pp]-1,F+1);
	dfs(pos[pp]+1,rig,F+1);
}
int main()
{
	freopen("tower.in","r",stdin);
	freopen("tower.out","w",stdout);
	init();
	sol();
	dfs(1,n,0);
	for(int i=1;i<=n;i++)
	{
		cout<<per[i].flo<<' '; 
	}
}

同寝大佬用单调栈优化到了O(n),这里也把代码放出来(无删改):

#include<bits/stdc++.h>//貌似要做单调栈?   每个数都是它左边或右边第一个大于它的儿子(取小的) 
using namespace std;
const int N = 1e5 + 10;
int n, a[N], cnt[N], L[N], R[N], head[N], tot, st;
stack< int > s;
struct edge{
    int v, last;
}E[N * 2];
void Get(){
    for(int i = 1; i <= n; i++){ 
        L[i] = R[i] = 1e6;
    } 
    for(int i = 1; i <= n; i++){//找每个数的两边第一个大于它的数 
        while(!s.empty() && a[i] > a[s.top()]){
            int t = s.top();
            R[t] = i;//右边第一个比它大的
            s.pop(); 
        }
        if(!s.empty()){
            int t = s.top();
            L[i] = t;//左边第一个比他大的 
        }
        s.push(i);
    }
}
void add(int u, int v){
    E[++tot].v = v;
    E[tot].last = head[u];
    head[u] = tot;
}
void dfs(int s){
    for(int i = head[s]; i != 0; i = E[i].last){
        int v = E[i].v;
        cnt[v] = cnt[s] + 1;
        dfs(v);
    }
}
int main(){
    freopen("tower.in", "r", stdin);
    freopen("tower.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    Get(); 
    for(int i = 1; i <= n; i++){//建图 
        int l_max = L[i], r_max = R[i], f = 0; //L[i], R[i]分别表示左右两边比第i个数大的数 
        if(l_max == 1e6 && r_max == 1e6) st = i;
        else{
            if(l_max == 1e6) add(r_max, i);
            else if(r_max == 1e6) add(l_max, i);
            else{
                if(a[l_max] > a[r_max]) add(r_max, i);
                else add(l_max, i);
            }
        }
    }
    dfs(st); 
    for(int i = 1; i <= n; i++){
        if(i == 1) printf("%d", cnt[i]);
        else printf(" %d", cnt[i]);
    }
    printf("\n");
    return 0;
}


T3-钦定IOI选手

        看到中位数,显然与数的绝对大小无关,只与相对大小有关,于是我们可以把≥x的数改为1,<x的数都改为-1,得到一个新数列,此时便有结论:对于一个区间内的各数之和>0,其z则该序列的中位数一定≥x

        因此考虑每次二分x,记录 b 数组的前缀和和前缀最小值,在区间长度 ≥k 时差分计算一下即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll qr()
{
	ll x=0,f=0;char ch=0;
	while(!isdigit(ch)) {f|=ch=='-';ch=getchar();}
	while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return f? -x:x;
}
const ll maxn=1e6;
ll n,k; 
ll a[maxn];
ll t[maxn];
ll sum[maxn];
ll tot;
ll ans;
void init()
{
	n=qr();k=qr();
	for(int i=1;i<=n;i++)
		a[i]=qr();
}
bool check(int x)
{
	memset(t,0,sizeof(t));
	memset(sum,0,sizeof(sum));
	for(int i=1; i<=n; i++)
	{
		if(a[i]>=x) t[i]=1;
		else t[i]=-1;
	}
	for(int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+t[i]; 
	}
	tot=1e9;
	for(int i=k;i<=n;i++)
	{
		tot=min(tot,sum[i-k]);
		if(sum[i]>tot) return 1;
	}
	return 0;
}
void work()
{
	ll l=1,r=1e9;
	ll mid;
	while(l+1<r)
	{
		ll mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	if(check(r)) ans=r;
	else ans=l;
}
void print()
{
	cout<<ans;
}
int main()
{
	freopen("plunder.in","r",stdin);
	freopen("plunder.out","w",stdout);
	init();
	work();
	print();
}


 T4-攻打恶魔之巅

 

将问题抽象为图论问题。

将每一级台阶看作是一个节点。它可以连向的结点有两类,一类是步长范围内能到达的点,边长为 1。另一类是传送的点,边长为 0,但是这个要消耗一次传送次数的。于是很容易就想到了分层图最短路。

设 di,j 为消耗了 j 次传送由 1 走到 i 点的最小步数。当通过第一类边转移的时候(记转移到的点为 v), 更新的是 dv,j, 第二类的转移更新的是 dv,j+1。

我们知道 边权要么是 1,要么是 0 ,所以用 01bfs 转移就可以得到 100 分了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int qr()
{
	ll x=0,f=0;char ch=0;
	while(!isdigit(ch)) {f|=ch=='-';ch=getchar();}
	while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return f? -x:x;
}
int n,m,k;
const int maxn=500100;
struct fun
{
	int y,v,nxt;
}edge[25000100];
int link[maxn];
int len;
int a[maxn];
int dis[maxn][30];
bool vis[maxn][30];
int ans=2e9;
struct node
{
	int pos,vav;
};
deque <node> que;
void insert(int xx,int yy,int val)
{
	edge[++len].nxt=link[xx];
	link[xx]=len;
	edge[len].y=yy;
	edge[len].v=val;
}
void init()
{
	n=qr();m=qr();k=qr();
	for(int i=1;i<=n;i++)
	{
		a[i]=qr();
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=max(i-m,1);j<=min(i+m,n);j++)
		{
			insert(i,j,1);
		}
	}
}
void DIJ()
{
	memset(dis,10,sizeof(dis));
	que.push_back((node){1,0});
	dis[1][0]=0;
	while(!que.empty())
	{
		node py=que.front();
		que.pop_front();
		if(vis[py.pos][py.vav]) continue;
		vis[py.pos][py.vav]=1;
		if(a[py.pos]&&py.vav!=k)
		{
			if(!vis[a[py.pos]][py.vav+1]&&dis[a[py.pos]][py.vav+1]>dis[py.pos][py.vav])
			{
				dis[a[py.pos]][py.vav+1]=dis[py.pos][py.vav];
				que.push_front((node){a[py.pos],py.vav+1});
			}
		}
		for(int i=link[py.pos];i;i=edge[i].nxt)
		{
			if(!vis[edge[i].y][py.vav]&&dis[edge[i].y][py.vav]>dis[py.pos][py.vav]+edge[i].v)
			{
				dis[edge[i].y][py.vav]=dis[py.pos][py.vav]+edge[i].v;
				que.push_back((node){edge[i].y,py.vav});
			}
		}
		
	}
}
void work()
{
	DIJ();
	for(int i=0;i<=k;i++)
	{
		ans=min(ans,dis[n][i]);
	}
	cout<<ans;
}
int main()
{
	freopen("step.in","r",stdin);
	freopen("step.out","w",stdout);
	init();
	work();
}

注:用DP也可以A掉这道题 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值