2023牛客寒假算法基础集训营2_20230118「典 单独考虑算贡献」「预处理/模」「巧 按度建图/根号分治」「典 差分处理」「数学+二分」「典 聪明枚举」

5/12

不好,说不清这是哪种题,可能是思维(?技巧(?经验(?题,赛时我会手推但是不会写代码,反正写着不顺。

已过非太水的D

//https://ac.nowcoder.com/acm/contest/46810/D
//只考虑深度_小典

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int f[N],arr[N],num[N];
vector<int> a[N];

int find(int n){
    int cnt=1;int t=n;
    while(t!=1){
        cnt++;t=f[t];
    }
    return cnt;
}

void bfs(){
    queue<pii>q;
    q.push(m_p(1,1));
    while(!q.empty()){
        pii t=q.front();q.pop();
        num[t.first]=t.second;
        for(int i=0;i<a[t.first].size();++i){
            q.push(m_p(a[t.first][i],t.second+1));
        }
    }
}

void work() {
    int n;cin>>n;
    for(int i=2;i<=n;++i){
        int b;
        cin>>b;
        a[b].pb(i);
    }for(int i=1;i<=n;++i){
        cin>>arr[i];
    }sort(arr+1,arr+1+n);
    
    bfs();
    sort(num+1,num+1+n);
    
    int ans=0;
    for(int i=1;i<=n;++i){
        ans+=num[i]*arr[i];
    }
    
    cout<<ans<<'\n';
}

signed main() {
	io;
	work();
	return 0;
}

H.

H-Tokitsukaze and K-Sequence_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

赛时用的upper_bound过了,但觉得有必要记一记典。

单独考虑算贡献。 

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int sum,cnt[N];

void work() {
    mem(cnt,0);
    multiset<int>v;//如果这里用vector之后sort的话会t耶
    int n;cin>>n;
    for(int i=1;i<=n;++i){
        int a;
        cin>>a;cnt[a]++;//记录出现次数的数组
    }
    
    for(int i=1;i<=1e5+10;++i){
        if(cnt[i]) v.insert(cnt[i]);//排set
    }
    
    int ge=v.size();//还有多少种数在k个分组中无法做到每组至多有一个
    sum=0;//可以做到上述的数的个数(注意不是种数),这是每个k的一部分贡献
    for(int i=1;i<=n;++i){
        while(ge>0&&(*v.begin())<=i){//当k增大到有一种数可以满足了
            sum+=(*v.begin());       //该数的贡献已经确定不会再加了
            v.erase(v.begin());      //把该数从做不到的set中删掉
            ge--;
        }
        cout<<sum+ge*(i-1)<<'\n';  //答案是确定的贡献+做不到的数*(k-1)个数组里都仅有一个该数
    }
}

signed main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

L.

L-Tokitsukaze and Three Integers_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

预处理出arr[ i ]*arr[ j ] %p的值,枚举k,另开一个数组去重数组d记录当结果为x时有多少对ij是包含i的。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 5e3 + 50;
int arr[N],c[N],d[N][N];

void work() {
    int n,p;cin>>n>>p;
    for(int i=1;i<=n;++i){
        cin>>arr[i];
        arr[i]%=p;
    }
    
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(i==j) continue;//特别注意
            c[arr[i]*arr[j]%p]++;
            d[i][arr[i]*arr[j]%p]+=2;//因为我们的ij都是从1->n的循环所以记录每一个i*2即可
        }
    }
    for(int i=0;i<p;++i){
        int ans=0;
        for(int j=1;j<=n;++j){
            int tar=(i-arr[j]+p)%p;    //arr[i]*arr[j]%p
            ans+=c[tar]-d[j][tar];     //ans+=总对数-含k的对数
        }
        cout<<ans<<" \n"[i==p-1];
    }
}

signed main() {
	io;
	work();
	return 0;
}

 C.

C-Tokitsukaze and a+b=n (hard)_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

同L,所有i!=j的区间结果=所有结果-i==j的结果。

用一个差分数组记录每一个区间,前缀和一下得到num数组(每个数字出现次数),对于a+b=n的所有结果可以用num[a]*num[n-a]+num[b]*num[n-b]得到,然后减掉同一个区间里出现的所有结果,即区间取交。

(不是说不赋初值的时候自动赋0吗,怎么一直有4%的数据过不去memset了一下就过了。

------->2023.2.2更新,群友说开在全局的数组一定是0,局部的不一定。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mod 998244353 
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 4e5 + 50;

void work() {
    int n,m;
    cin>>n>>m;
    vector<pii> a(m+1);
    int c[N];mem(c,0);
    for(int i=1;i<=m;++i){
        cin>>a[i].first>>a[i].second;
        c[a[i].first]++;
        c[a[i].second+1]--;
    }
    for(int i=1;i<=N;++i){
        c[i]=c[i]+c[i-1];
        c[i]%=mod;
    }
    int ans=0;
    for(int i=1;i<=n;++i){
        ans+=c[i]*c[n-i]%mod;    //求所有情况
        ans%=mod;
    }
    for(int i=1;i<=m;++i){
        int l=a[i].first,r=a[i].second;
        int t=n-l,w=n-r;
        ans-=max(0ll,min(t,r)-max(w,l)+1);//区间取交
        (ans+=mod)%=mod;
    }
    cout<<ans<<'\n';
}

signed main() {
	io;
	work();
	return 0;
}

 K.

K-Tokitsukaze and Synthesis and Traits_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

妙妙题。第一次输入记录度,之后按度的大小建立有向图,这样在之后遍历的时候就可以避免很多出度的菊花图(?炸鸡块哥哥这么说的)浪费时间。 

可以去了解一下三元环计数。

(试了一下stl果然比直接开数组慢)

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

void work() {
    int n,m,q;cin>>n>>m>>q;
    vector<int>d[N];
    vector<int>a(m+1),b(m+1),c(n+1);
    for(int i=1;i<=m;++i){
        cin>>a[i]>>b[i];
        c[a[i]]++;c[b[i]]++;
    }
    for(int i=1;i<=m;++i){
        if(c[a[i]]>c[b[i]]) d[b[i]].pb(a[i]);
        else d[a[i]].pb(b[i]);
    }
    vector<int>now(n+1);
    for(int i=1;i<=q;++i){
        int ans=0;
        int k;cin>>k;
        vector<int>t(k+1);
        for(int j=1;j<=k;++j){
            cin>>t[j];now[t[j]]=i;
        }
        for(int j=1;j<=k;++j){
            for(auto to:d[t[j]]){
                if(now[to]==i) ans++;
            }
        }
        cout<<ans<<'\n';
    }
}

signed main() {
	io;
	work();
	return 0;
}

 F.

F-Tokitsukaze and Gold Coins (easy)_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

算是有一些限制的bfs/dfs(?,看到有个写不知道算不算双向bfs的,位运算很妙,感觉上次和位运算智慧博弈了一晚上之后对位运算没那么抵触了。 

(谔谔,就样例而言,手动清空数组比memset快了2倍,只清空需要的部分比清空全部快了4倍,int比bool快了1ms。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define m_p make_pair
#define mod 998244353
//#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 5e5 + 50;
int mp[4][N], f[4][N];
int vis[4][N];
int n, k;

void work() {
	cin >> n >> k;
	for(int i=1;i<=3;i++){
        memset(f[i],0,n*4+50);
        memset(mp[i],0,n*4+50);
        memset(vis[i],0,n*4+50);
    }
	while (k--) {
		int a, b;
		cin >> b >> a;
		vis[a][b] ^= 1;
	}
	int ans = 0;
	mp[3][n] = 1;
	for (int i = n; i >= 1; --i) {
		for (int j = 3; j > 0; --j) {
			if (vis[j][i])
				continue;
			mp[j][i] |= (mp[j + 1][i] | mp[j][i + 1]);
		}
	}
	mp[1][1] = 0;
	f[1][1] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= 3; ++j) {
			if (vis[j][i])
				continue;
			f[j][i] |= (f[j - 1][i] | f[j][i - 1]);
			if (f[j][i] && mp[j][i])
				ans++;
		}
	}
	cout << ans << '\n';
}

int main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

E.

E-Tokitsukaze and Function_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

数学+二分。

由均值不等式可知sqrt(n)时一定是最小值,但是加下取整后,该最小值就不一定唯一了。而且sqrt直接下取整还需要判断一下是下取整最小还是上取整最小。之后二分,我们可以知道对于所有下取整式,至少小于等于原式且结果相差不过1,于是可以想出下取整式的图像一定是如下平台。 

那我们需要找的一定是与sqrt同水平线上的最小x,所以二分成立条件应该只有==。

(jls:那就直接二分不要动脑子!        :)懂了,二分是无脑算法bushi)

 红线横坐标应该能接上的,画的很丑意会即可。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

void work() {
    int n,l,r;cin>>n>>l>>r;
    int t=sqrt(n);
    int p=l;
    int mn=(n/l+l-1);
    if(mn>n/r+r-1){
        p=r;
        mn=n/r+r-1;
    }
    if(t<=r&&t>=l&&mn>=n/t+t-1){
        p=t;
        mn=n/t+t-1;
    }
    t++;
    if(t<=r&&t>=l&&mn>n/t+t-1){
        p=t;
        mn=n/t+t-1;
    }
    
    int lo=l,hi=p;
    while(lo<hi){
        int mid=(lo+hi)/2;
        if(n/mid+mid-1==mn) hi=mid;
        else lo=mid+1;
    }
    cout<<lo<<'\n';
}

signed main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

 I.

I-Tokitsukaze and Musynx_2023牛客寒假算法基础集训营2 (nowcoder.com)

思路:

一些聪明的枚举。(jls:这个简单!

 对于每一个x[ i ],都存在4个h可以让该值发生改变,我们只需要枚举这4*n个h即可。开一个vector<pii>或者set(但是set去重会不会有影响呀没试不知道),记录<h,v[ j+1 ]-v[ j ] >,之后只需要一边枚举一边记录最大答案即可。

注意开闭区间鸭!

  • jls这样枚举的正确性说明:因为对pii的sort是先按first再按second,所以对于不同的h一定是从小到大排的,可以保证在当前res+的时候,小于h的所有影响已经全部加上了;对于相同的h,理论上每一种ans的变化都必须使res加全h的所有变化再去比较,但是由于h相同对second排序,若之前second之和大于res了,那么之后的所有second必为正,于是直到h结束的每一次ans都会被res更新;若之前的second之和小于res,那么在此之前ans不可能被res更新,如果之后res又增大了,我们也可以保证res已经加过了每一个比当前second小的影响。

(vector<pii>的时候用push_back(make_pair(a,b))或者emplace(a,b)

jls牛蛙!他的代码清晰明了高端优雅。从明天起培养书写好习惯,拒绝电子答辩。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;

void work() {
    int n;cin>>n;
    int x[n+1];
    for(int i=1;i<=n;++i){
        cin>>x[i];
    }
    int a,b,c,d;cin>>a>>b>>c>>d;
    d++;//开区间
    int v[6];
    for(int i=1;i<=5;++i){
        cin>>v[i];
    }
    
    int res=0;int ans=-inf;
    vector<pii> el;
    for(int i=1;i<=n;++i){
        res+=v[1];
        el.emplace_back(a-x[i],v[2]-v[1]);
        el.emplace_back(b-x[i],v[3]-v[2]);
        el.emplace_back(c-x[i],v[4]-v[3]);
        el.emplace_back(d-x[i],v[5]-v[4]);
    }
    ans=res;
    sort(el.begin(),el.end());
    for(auto[a,x]:el){
        res+=x;
        ans=max(ans,res);
    }
    cout<<ans<<'\n';
}

signed main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

 小小总结

线段树要不算了,鸽鸽。最近好摆,快救救我。

火车头越来越长了。

我真的很不会那种言语难以说明白的题(比如出现次数个数),可能就是脑子线程不够用理不清楚,总会有怪里怪气但是代码又写不了的手推方法,大概是做题太少见得不多。

二分/各种变化的搜索不熟&&思维不好。感觉有的方法想不到是因为没见过,有的是因为对题意||算法理解不到位,有的就是因为脑子不好。(要是脑子好+理解透没见过也能想出来。

卡常真是一门艺术。

jiang宝的本质是复读智能人吗可爱可爱可爱。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值