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;
}

浙公网安备 33010602011771号