The 2024 CCPC Shandong Invitational Contest and Provincial Collegiate Programming Contest

Luckyblock發表於2024-10-11

目錄
    • 寫在前面
    • I 簽到
    • A 二分答案
    • F 簡單推式子,列舉,排序
    • K 構造
    • D 列舉
    • C DP,計數
    • J 最小生成樹,貪心
    • E 線段樹,均攤複雜度
    • H 二分圖
    • 寫在最後
  • 學到了什麼:

寫在前面

比賽地址:https://codeforces.com/gym/105385

以下按個人向難度排序。

wenqizhi 大爹跑去軍訓了於是和 dztlb 兩個人打打邀請賽爽爽。

I 簽到

當且僅當兩個字元相鄰且相等時有解,檢查一下有解的最少步數即可。

code by dztlb:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int T,n;
char s[N];
signed main(){
	cin>>T;
	while(T--){
		scanf("%s",s);
		n=strlen(s);
		bool fl=0;
		for(int d=0;d<=n;++d){
			if(s[(0+d)%n]==s[(d+n-1)%n]){
				fl=1;
				cout<<d<<'\n';
				break;
			}
		}
		if(!fl) puts("-1");
	}
	return 0;
}

A 二分答案

二分答案一下做完了。

注意在 check 的時候可能爆 LL

code by dztlb:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+5;
const int inf=2e18;
int T,n,k,t[N],l[N],w[N];
bool check(int m){
	int cnt=0;
	for(int i=1;i<=n;++i){
		int o=t[i]*l[i]+w[i];
		cnt+=(m/o)*l[i];
		cnt+=min((m%o)/t[i],l[i]);
		if(cnt>=k) return 1;
	}
	return 0;
}
signed main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;++i){
			cin>>t[i]>>l[i]>>w[i];
		}
		int L=0,R=inf,ans=0;
		while(L<=R){
			int mid=(L+R)>>1;
			if(check(mid)){
				ans=mid;
				R=mid-1;
			}else L=mid+1;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

F 簡單推式子,列舉,排序

把最終式子列出來,相鄰項相減一下,發現對於 \(k=i\) 的答案,等價於選擇 \(k\) 個字尾和,且欽定 \(\operatorname{suf}_1\) 必選時的最大值。

於是把求得字尾和降序排個序直接取即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n;
LL a[kN], suf[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    suf[n + 1] = 0;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = n; i; -- i) suf[i] = suf[i + 1] + a[i];
    
    LL ans = suf[1];
    std::cout << ans << " ";
    std::sort(suf + 2, suf + n + 1, std::greater<LL>());
    for (int i = 2; i <= n; ++ i) {
      ans += suf[i];
      std::cout << ans << " ";
    }
    std::cout << "\n";
  }
  return 0;
}

K 構造

考慮把唯一的四個角不同的矩形放到左下角。

然後為了保證其他矩形四個角至少兩個相同,一個很顯然的做法是令其他部分關於對角線對稱即可,則可令左上角和右下角的位置相同。

然後就很容易了,詳見程式碼。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1005;
int n;
int a[55][55];
signed main(){
	cin>>n;
	puts("Yes");
	a[1][1]=1,a[1][2]=2;
	a[2][1]=3,a[2][2]=4;
	for(int i=3;i<=50;++i){
		a[1][i]=5;
		a[2][i]=5;
		a[i][1]=5;
		a[i][2]=5;
	}
	int now=6;
	for(int i=3;i<=50;i+=2){
		a[i][i]=now;
		a[i+1][i]=now+1;
		a[i][i+1]=now+1;
		a[i+1][i+1]=now+2;
		for(int j=i+2;j<=50;++j){
			a[i][j]=now+3;
			a[i+1][j]=now+3;
			a[j][i]=now+3;
			a[j][i+1]=now+3;
		}
		now+=4;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			cout<<a[i][j]<<' ';
		} cout<<endl;
	}
	return 0;
}

D 列舉

大眼觀察一下這個資料範圍感覺很奇怪啊,\(T=500\)?為啥不出到 \(10^5\)?於是猜測複雜度可能是根號的。

先不考慮時間的限制,則顯然最優策略是先購買 \(\left\lfloor\frac{m}{p}\right\rfloor\) 袋麵粉,然後賣掉,並不斷重複這個過程。在此過程中每輪買入賣出麵粉的數量是單調不降的,且很容易計算出經過多少輪之後,一輪中能買入更多面粉。

再考慮時間的限制,買入賣出 \(x\) 袋麵粉至少花費時間 \(2x\),容易發現上述過程中,\(\left\lfloor\frac{m}{p}\right\rfloor\) 的取值只有至多 \(\sqrt{t}\) 種。

於是不斷列舉計算一輪中買入賣出麵粉的數量,直至時間不夠用即可,特判下剩餘時間內還能進行多少輪,以及最後一輪至多買入賣出多少麵粉即可。

總時間複雜度 \(O(T\sqrt t)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
LL p, a, b, q, c, d, m, t;
//=============================================================
LL needt(LL x_) {
  return a * x_ + b + c * x_ + d;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> p >> a >> b;
    std::cin >> q >> c >> d;
    std::cin >> m >> t;

    while (1) {
      LL x = m / p, delta = p * (x + 1) - m;
      if (x == 0) break;

      LL k = ceil(1.0 * delta / (x * (q - p)));

      bool flag = 0;
      if (1ll * k * needt(x) > t) {
        flag = 1;
        k = t / needt(x);
      }
      t -= 1ll * k * needt(x);
      m += 1ll * k * x * (q - p);
      if (flag) {
        if (t <= b + d) x = 0;
        else x = std::min(x, (t - b - d) / (a + c));
        m += 1ll * x * (q - p);
        break;
      }
    }
    std::cout << m << "\n";
  }
  return 0;
}

C DP,計數

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+5;
const int mod=998244353;
int n,k,ans=1;
struct node{
	int ty,id;
}p[N*2];
bool cmp(node a,node b){
	if(a.id==b.id) return a.ty<b.ty;
	return a.id<b.id;
}
int T;
signed main(){
	cin>>T;
	while(T--){
		ans=1;
		cin>>n>>k;
		for(int i=1;i<=n;++i){
			cin>>p[i].id;
			p[i].ty=1;
			cin>>p[i+n].id;
			p[i+n].ty=2;
		}
		sort(p+1,p+1+2*n,cmp);
		int now=0;
		for(int i=1;i<=2*n;++i){
			if(p[i].ty==1){
				ans*=(k-now); ans%=mod;
				++now;
			}else{
				--now;
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}

J 最小生成樹,貪心

「CSP-SJX2019」網格圖 的思想基本一樣的一題。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e3 + 10;
//=============================================================
int n;
struct Edge {
  int u, v, w;
} e[kN * kN];
int fa[kN], yes[kN];
LL ans, sz[kN];
//=============================================================
bool cmp(const Edge& fir_, const Edge& sec_) {
  return fir_.w < sec_.w;
}
int find(int x_) {
  return x_ == fa[x_] ? x_ : fa[x_] = find(fa[x_]);
}
void merge(int x_, int y_, int w_) {
  int fx = find(x_), fy = find(y_);
  if (fx == fy && yes[x_] && yes[y_]) return ;

  if (yes[x_] && yes[y_]) {
    ans += w_;
  } else if (yes[x_]) {
    ans += 1ll * sz[y_] * w_;
  } else if (yes[y_]) {
    ans += 1ll * sz[x_] * w_;
  } else if (x_ == y_) {
    ans += 1ll * (sz[x_] - 1) * w_;
  } else {
    ans += 1ll * (sz[x_] + sz[y_] - 1) * w_;
  }
  yes[x_] = yes[y_] = 1;
  fa[fx] = fy;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> sz[i], fa[i] = i, yes[i] = 0;
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        int w; std::cin >> w;
        e[(i - 1) * n + j] = (Edge) {i, j, w};
      }
    }
    std::sort(e + 1, e + n * n + 1, cmp);
    ans = 0;
    for (int i = 1; i <= n * n; ++ i) {
      auto [u, v, w] = e[i];
      merge(u, v, w);
    }
    std::cout << ans << "\n";
  }
  return 0;
}

E 線段樹,均攤複雜度

好玩題!場上和 std 做法基本一致了但是想的是根號分治於是 T 爆了,教訓是 \(O(1)\) 合併的資訊直接上線段樹就行。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, m, cnt[kN];
LL ans;
//=============================================================
namespace seg {
  #define mid ((L_+R_)>>1)
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  const int kNode = kN << 2;
  int sum[kNode], tag[kNode];
  std::vector<int> query[kNode];
  void pushup(int now_) {
    sum[now_] = sum[ls] + sum[rs];
  }
  void check(int now_, int L_, int R_) {
    int len = R_ - L_ + 1, newtag = 0, delta = 0;
    if (tag[now_] == 2) return ;
    if (tag[now_] == 1 && sum[now_] == 0) {
      newtag = 2, delta = 1;
    } else if (tag[now_] == 0 && sum[now_] == 1) {
      newtag = 1, delta = len - 1;
    }

    if (!newtag) return ;
    tag[now_] = newtag;
    for (auto id: query[now_]) {
      if (cnt[id] == 1) ans -= 1ll * id * id;
      cnt[id] -= delta;
      if (cnt[id] == 1) ans += 1ll * id * id;
    }
  }
  void build(int now_, int L_, int R_) {
    query[now_].clear();
    sum[now_] = tag[now_] = 0;
    if (L_ == R_) {
      sum[now_] = 1;
      tag[now_] = 1;
      return ;
    }
    build(ls, L_, mid), build(rs, mid + 1, R_);
    pushup(now_);
  }
  void insert(int now_, int L_, int R_, int l_, int r_, int id_) {
    if (l_ <= L_ && R_ <= r_) {
      query[now_].push_back(id_);
      return ;
    }
    if (l_ <= mid) insert(ls, L_, mid, l_, r_, id_);
    if (r_ > mid) insert(rs, mid + 1, R_, l_, r_, id_);
  }
  void modify(int now_, int L_, int R_, int pos_) {
    if (L_ == R_) {
      sum[now_] = 0;
      check(now_, L_, R_);
      return ;
    }
    if (pos_ <= mid) modify(ls, L_, mid, pos_);
    else modify(rs, mid + 1, R_, pos_);
    pushup(now_), check(now_, L_, R_);
  }
  #undef mid
  #undef ls
  #undef rs
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    seg::build(1, 1, n);
    ans = 0;
    for (int i = 1; i <= m; ++ i) {
      int l, r; std::cin >> l >> r;
      ++ l, ++ r;
      seg::insert(1, 1, n, l, r, i);
      cnt[i] = r - l + 1;
      if (cnt[i] == 1) ans += 1ll * i * i;
    }
    std::cout << ans << " ";
    for (int i = 1; i <= n; ++ i) {
      LL a; std::cin >> a;
      seg::modify(1, 1, n, (a + ans) % n + 1);
      std::cout << ans << " ";
    }
    std::cout << "\n";
  }
  return 0;
}
/*
3
5 4
2 4
2 3
3 3
0 2
3 2 4 2 0
2 1
1 1
1 0
2 1
0 1
0 0

1
2 1
0 1
0 0
*/

H 二分圖

場上爆寫費用流居然調出來了但是發現費用流只能做第一問第二問假了於是寄寄。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 1e3 + 10;
const int kM = 2e5 + 10;
//=============================================================
int housenum, bannum, n, maxnode;
int edgenum, head[kN], v[kM], ne[kM];
struct Blank {
  int l, r, id;
};
std::vector<pii> ans;
std::map<pii, pii> point;
std::map<int, std::set<pii> > rhouse, chouse;
std::map<int, std::vector<Blank> > rnode, cnode;
int match[kN];
bool vis[kN];
//=============================================================
void Add(int u_, int v_) {
  v[++ edgenum] = v_; 
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
bool init() {
  std::cin >> housenum;

  edgenum = 0;
  n = 0, maxnode = 4 * housenum;
  for (int i = 1; i <= maxnode; ++ i) head[i] = match[i] = 0;
  ans.clear(), point.clear();
  rhouse.clear(), chouse.clear(), rnode.clear(), cnode.clear();

  for (int i = 1; i <= housenum; ++ i) {
    int r, c; std::cin >> r >> c;
    rhouse[r].insert(mp(c, 0));
    chouse[c].insert(mp(r, 0));
  }
  std::cin >> bannum;
  for (int i = 1; i <= bannum; ++ i) {
    int r, c; std::cin >> r >> c;
    rhouse[r].insert(mp(c, 1));
    chouse[c].insert(mp(r, 1));
  }

  int flag = 0;
  for (auto [r, s]: rhouse) {
    int last = -1;
    for (auto [c, type]: s) {
      if (last != -1 && type == 0) {
        flag |= (c == last + 1);
        rnode[r].push_back((Blank){last + 1, c - 1, ++ n});
      }
      last = type ? -1 : c;
    }
  }
  for (auto [c, s]: chouse) {
    int last = -1;
    for (auto [r, type]: s) {
      if (last != -1 && type == 0) {
        flag |= (r == last + 1);
        cnode[c].push_back((Blank){last + 1, r - 1, ++ n});
      }
      last = type ? -1 : r;
    }
  }
  if (flag) return true;
  
  for (auto [r, sr]: rnode) {
    for (auto [l1, r1, id1]: sr) {
      for (auto [c, sc]: cnode) {
        if (c < l1 || r1 < c) continue;
        for (auto [l2, r2, id2]: sc) {
          if (r < l2 || r2 < r) continue;
          Add(id1, id2);
          point[mp(id1, id2)] = mp(r, c);
        }
      }
    }
  }
  return false;
}
bool dfs(int u_) {
	for (int i = head[u_]; i; i = ne[i]) {
		int v_ = v[i];
		if (vis[v_]) continue;
		vis[v_] = 1;
		if (!match[v_] || dfs(match[v_])) {
			match[v_] = u_;
			return 1;
		}
	}
	return 0;
}
void solve() {
  for (int i = 1; i <= n; ++ i) {
		for (int j = 1; j <= n; ++ j) vis[j] = 0;
	  dfs(i);
	}
  for (int i = 1; i <= n; ++ i) {
    if (match[i]) {
      ans.push_back(point[mp(match[i], i)]);
      auto [r, c] = ans.back();
      rhouse[r].insert(mp(c, 1));
      chouse[c].insert(mp(r, 1));
    }
  }

  for (auto [r, s]: rhouse) {
    int last = -1;
    for (auto [c, type]: s) {
      if (last != -1 && type == 0) {
        ans.push_back(mp(r, c - 1));
      }
      last = type ? -1 : c;
    }
  }
  for (auto [c, s]: chouse) {
    int last = -1;
    for (auto [r, type]: s) {
      if (last != -1 && type == 0) {
        ans.push_back(mp(r - 1, c));
      }
      last = type ? -1 : r;
    }
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    if (init()) {
      std::cout << -1 << "\n"; 
      continue;
    }
    solve();
    std::cout << ans.size() << "\n";
    for (auto [x, y]: ans) std::cout << x << " " << y << "\n";
  }
  return 0;
}

寫在最後

學到了什麼:

相關文章