2025 ICPC Wuhan Invitational Contest (The 3rd Universal Cup. Stage 37: Wuhan)

I. Bingo 3

简单构造,大数尽量往对角线填。

队友开的

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;

void solve(){
    int n,k;
	std::cin >> n >> k;
	auto a = std::vector(n,std::vector(n,0));
	auto ans = [&] {
		auto value = std::vector(n * n + 1,false);
		for(auto i = 0; i != n; ++i) {
			a[0][i] = k - i;
			if(k - i <= 0) {
				return false;
			}
			value[k - i] = true;
		}
		for(auto i = 1; i != n; ++i) {
			a[i][i] = k + i;
			if(k + i > n * n) {
				return false;
			}
			value[k + i] = true;
		}
		auto get = [&,it = 1]() mutable {
			while(value[it]) {
				++it;
				continue;
			}
			value[it] = true;
			return it;
		};

		for(auto i = 0; i != n; ++i){
			for(auto j = 0; j != n; ++j) {
				if(a[i][j]) {
					continue;
				}
				a[i][j] = get();
			}
		}
		return true;
	}();
	if(not ans) {
		std::cout << "No\n";
		return;
	}
	std::cout << "Yes\n";
	for(auto i = 0; i != n; ++i) {
		for(auto j = 0; j != n; ++j) {
			std::cout << a[i][j] << ' ';
		}
		std::cout << '\n';
	}

}

signed main(){
	int t=1;
	cin>>t; 
	
	while(t--){
		solve(); 
	}
}

A. Problem Setting

区间合并板子

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
using namespace std;
using pii=std::pair<int,int>;
using ll = long long;
using ull = unsigned long long;
const ll inf = 1e18;
const int mod = 998244353;

void solve(){
    int n,m;
    cin>>n>>m;

    vector<int> a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }

    vector<int> l(n+1,-inf),r(n+1,inf);

    while(m--){
        int p,a,b;
        cin>>p>>a>>b;
        l[p]=max(l[p],a);//1-5,6-7
        r[p]=min(r[p],b);
    }

    int ans=0;

    for(int i=1;i<=n;i++){
        if(l[i]>r[i]){
            cout<<-1<<endl;
            return;
        }
        else{
            if(a[i]>=l[i] && a[i]<=r[i]){
                
            }
            else{
                ans+=min(abs(a[i]-l[i]),abs(a[i]-r[i]));
            }
        }
    }

    cout<<ans<<endl;
}

signed main(){
	int t=1;
	std::cin>>t; 
	
	while(t--){
		solve(); 
	}
}

L. Subsequence

\(n^2\) 的枚举起点和终点,然后去找范围内合法的位置,找一个尽量贴近最中间的即可,需要处理一些细节

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
using ll=long long; 
using pii=pair<int,int>;
const ll inf = 1e18;
const int mod = 1e9+7;

void solve(){
    int n,ans=1;
    cin>>n;

    vector<int> a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }

    sort(a.begin()+1,a.end());
    unordered_map<int,int> l,r;

    for(int i=1;i<=n;i++){
        if(!l[a[i]]) l[a[i]]=i;
    }
    for(int i=n;i>=1;i--){
        if(!r[a[i]]) r[a[i]]=i;
    }

    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if((a[i]+a[j])&1) continue;
            if(a[i]==a[j]){
                ans=max(ans,j-i+1);
                continue;
            }
            int mid=(a[i]+a[j])/2;
            int pos=(i+j)/2;

            if(l[mid]==0) continue;
            if(l[mid]<=pos && pos<=r[mid]){
                ans=max(ans,j-i+1);
            }
            else if(pos<=l[mid]){
                int cntl=(l[mid]-i-1);
                int cntr=(j-l[mid]-1);
                ans=max(ans,cntr*2+3);
            }
            else if(pos>=r[mid]){
                int cntl=(r[mid]-i-1);
                int cntr=(j-r[mid]-1);
                if(cntl==cntr){
                    ans=max(ans,cntl*2+3);
                }
                else{
                    ans=max(ans,(cntl)*2+1+3);
                }
            }
            // if(ans==6) cout<<i<<" "<<j<<endl;

        }
    }

    cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    int ct=1;
    cin>>ct;

    while(ct--) solve();

    return 0;
}

F. Knapsack

妙妙贪心+二进制题

先给出一个放法,再考虑正确性:

按照物品大小,从大到小往背包里放

先放最大的物品,每个背包可以放 \(a[i]/m\) 个(层)每层的大小是 \(b[i]\),如果还有剩余,则需要再开一层,同时记录下此时已开辟且空闲的位置大小

再考虑下一个物品,优先把下一个物品放入上一个物品留下的已开辟且空闲的空间中,如果把这个空间放满且还有剩余,则开辟新的一层空间

过程中会有一个问题,如何表示空闲空间的大小?肯定不能用真实数值表示,因为太大。有一个显而易见的想法是,用二的次幂的多项式来表示:类似这样:\(2e100-2e67-2e4\)

类似这样的一个东西,但是,这个还是很难去计算。所以可以考虑,将这个多项式中,所有二的次幂都转化为当前枚举到的物品的次幂,换句话说,用当前物品的大小作为衡量剩余空间大小的单位一

因为都是二的次幂,所以在处理当前剩余空间大小时,如果要改变单位一,只需要不断乘二即可。

此时会有问题:不断乘二,还是会爆 ll。这里注意到,而当剩余空间,在当前物品大小作为单位一时,空间大小如果超过某一个阈值,则可以装下后面所有物品

如何计算这个阈值?

每个物品最多有 \(1e9\) 个,而:\(1e9*8~>~1e9*4+1e9*2+1e9*1\)

所以这个阈值就是 \(1e9\),当然在比赛时不想计算,直接 开一个超大数也没关系

证明正确性:

因为大小都是二的次幂,所以大物品开辟的空闲空间,只要小物品足够多,就一定能填满,不会出现传统背包问题中,放了大物品,剩余空间放不了小物品的情况。

所以贪心是对的

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
using ll=long long; 
using pii=pair<int,int>;
const ll inf = 1e18;
const int mod = 998244353;

int qmi(int a,int b,int p){
    int res=1;
    while(b){
        if(b&1) res=res*a%p;
        b>>=1;
        a=a*a%p;
    }
    return res;
}

void solve(){
    int n,m;
    cin>>n>>m;

    vector<int> a(n+1),b(n+1);
    vector<pii> t(n+1);
    int ans=0;

    for(int i=1;i<=n;i++){
        cin>>t[i].second>>t[i].first;
    }
    sort(t.begin()+1,t.end());

    for(int i=1;i<=n;i++){
        a[i]=t[i].second;
        b[i]=t[i].first;
    }

    //当前已经开辟但还未被填满的空间大小
    //以当前物品的大小作为 now 的单位一
    int now=0,preb=0;
    while(b.size()){
        if(now==0){
            //要开新的一层或多层
            int floor=(a.back()+m-1)/m;
            int diff=a.back()%m;

            ans+=floor*qmi(2,b.back(),mod)%mod;
            ans%=mod;
            //现在在最新的一层有 now 个大小为 bi 的空位
            if(diff){
                now=m-diff;
                preb=b.back();
            }

            b.pop_back();
            a.pop_back();
        }
        else{
            int diffb=preb-b.back();
            //将单位1转换为当前物品的大小
            for(int i=1;i<=diffb;i++){
                now*=2;
                //如果非常大,则可以放下后续所有物品,直接结束
                //这里的阈值为止,推测是 2e9,但开大一点
                if(now>=1e15){
                    cout<<ans<<endl;
                    return;
                }
            }
            if(now>=a.back()){
                preb=b.back();
                now-=a.back();
                a.pop_back();
                b.pop_back();
            }
            else{
                a.back()-=now;
                now=0;
            }
        }
    }

    cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    int ct=1;
    cin>>ct;
    while(ct--) solve();

    return 0;
}

G. Path Summing Problem

tag: 贡献法,根号分治,容斥,DP

首先考虑,每个位置对答案的贡献

当前位置的数值为 \(k\),则当前位置的贡献是:在所有经过当前位置的路径中,以当前位置作为路径中第一个 \(k\) 的路径数量

考虑 \(O(nm)\) 的做法,是容易的,就是一个简单的 DP 变形,看代码很好理解。

\(t[x][y]\) 表示,从 \(1,1\) 走到 \(x,y\),只能往下或往右的路径数量

考虑 \(O(k^2)\) 的做法,设 \(dp[i]\) 表示,以位置 \(i\) 为终点,且位置 \(i\) 是路径中第一个 \(k\) 的路径数量

初始时,\(f[i]=t[x][y]\) (\(x,y\)\(i\) 的坐标),还需要减去非法的路径数量,也就是:从其他数值为 \(k\) 的点走到 \(i\) 的路径数量

此时可以枚举所有数值同样为 \(k\),且可以走到 \(i\) 的点 \(j\):

\(f[i] -= f[j]*t[x-a+1][y-b+1]\)

统计完答案后,对 \(ans\) 累加 \(f[i]*t[n-x+1][m-y+1]\) 即可

单独考虑这两种方法,最坏都是 \(O(1e5 * 1e5)\)

设阈值 \(B= \sqrt(n*m)\)

当数值为 \(k\) 的位置数量小于 \(B\) 时,可以使用 \(O(k^2)\) 的做法,此时复杂度最多是:\(O(n*m/k * k^2) = O(n*m * \sqrt(n*m))\)

当数值为 \(k\) 的位置数量大于 \(B\) 时,可以使用 \(O(n*m)\)的做法,因为这种的 \(k\) 不会超过 $ \sqrt(n+m)$ 个,所以复杂度最多是:\(O(n*m * \sqrt(n*m))\)

根号分治

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
using ll=long long; 
using pii=pair<int,int>;
const ll inf = 1e18;
const int mod = 998244353;

void solve(){
    int n,m;
    cin>>n>>m;

    vector g(n+1,vector<int>(m+1));
    vector<vector<pii>> p(n*m+1);
    auto t=g;
    t[1][1]=1;
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>g[i][j];
            p[g[i][j]].push_back({i,j});

            if(i==1 && j==1) continue;
            t[i][j]=t[i-1][j]+t[i][j-1];
            t[i][j]%=mod;
        }
    }

    int ans=0;
    int B=sqrt(n*m);

    for(int k=1;k<=n*m;k++){
        if(p[k].size()==0) continue;

        if(p[k].size()<B){
            //做 k^2 的容斥
            vector<int> f(p[k].size());
            //f[i]: 以位置i为终点,且位置i是路径中第一个k的路径数量
            int now=0;

            for(int i=0;i<p[k].size();i++){
                auto [x,y]=p[k][i];
                f[i]=t[x][y];
                
                for(int j=0;j<i;j++){
                    auto [a,b]=p[k][j];
                    if(a>x || b>y) continue;

                    f[i]-=f[j]*t[x-a+1][y-b+1]%mod;
                    f[i]=(f[i]+mod)%mod;
                }
                now=(now+f[i]*t[n-x+1][m-y+1])%mod;
            }
            ans=(ans+now)%mod;
        }
        else{
            //O(n*m)
            vector f(n+1,vector<int>(m+1));
            f[1][1]=1;

            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    if(i!=1 || j!=1){
                        f[i][j]=(f[i-1][j]+f[i][j-1])%mod;
                    }
                    if(g[i][j]==k){
                        ans=(ans+f[i][j]*t[n-i+1][m-j+1])%mod;
                        f[i][j]=0;
                    }
                }
            }
        }
    }

    cout<<ans<<endl;

}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    int ct=1;
    cin>>ct;
    while(ct--) solve();

    return 0;
}
posted @ 2026-03-11 11:40  LYET  阅读(6)  评论(0)    收藏  举报