21.10 杂题
agc019b
二分答案,记 \(cnt_{i}\) 代表目前第 \(i\) 项活动作为多少个人的首选,每次把不能选的活动去除,最后如果所有人都满意了就结束。复杂度 \(O(nm\log{n})\)
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn = 305;
template <typename T>
void read(T &x) {
T flag = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
x *= flag;
}
int n, m, a[maxn][maxn], cnt[maxn], rk[maxn];
bool vis[maxn];
vector<int> vec[maxn];
bool check(int mid) {
int alive = m;
for (int i = 1; i <= m; i++) cnt[i] = 0, vis[i] = 1, vec[i].clear();
for (int i = 1; i <= n; i++) cnt[a[i][1]]++, vec[a[i][rk[i] = 1]].pb(i);
while (1) {
bool flag = 0;
for (int j = 1; j <= m; j++) {
if (vis[j]) {
if (cnt[j] > mid) {
vis[j] = 0;
flag = 1;
alive--;
for (int k : vec[j]) {
cnt[j]--;
while (rk[k] < m && !vis[a[k][rk[k]]])rk[k]++;
if (!vis[a[k][rk[k]]]) return 0;
vec[a[k][rk[k]]].pb(k);
cnt[a[k][rk[k]]]++;
}
}
}
}
if (!flag && alive) return 1;
if (!alive) return 0;
}
return 0;
}
int main() {
read(n); read(m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
read(a[i][j]);
int l = 1, r = n, ans = n;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}
然后仔细思考了一下我们这个过程,好像二分是不必要的。直接贪心,每次把出现次数最大的那个干掉,然后更新答案即可。但是效率差得不多。
CF550D
根据奇偶性分析一下发现当 \(k\) 是偶数时必定无解。考虑奇数时的构造,我们考虑构造两个完全相同的图然后两边连一下。显然造一个 \(k+2\) 个点的完全图然后把 \((4,5),(6,7)...(k+1,k+2),(1,2),(1,3)\)。 删掉即可。
AGC010E
对于不互质的点对后手显然不能改变他们的相对关系,所以对于两个不互质的点对 \((i,j)\) 将小的连向大的,最后拓扑排序即可,而由于后手可以改变互质的数的相对位置,每次取出最大的数即可。
然后这个东西假了。。。
看了下题解,发现不能直接那么定向,因为后手其实还是有办法的。
对于每个连通块,我们保存一棵生成树,每个点向没到过的点从小到大依次连边,并递归。这样做的好处是让 \(u\) 的两个儿子 \(v_1,v_2,a_{v_1}<a_{v_2}\) 不一定都连向 \(u\),因为这个时候如果 \(v_2\) 能排在 \(v_1\) 后面的话(即 \(v_1,v_2\) 也互质)那么显然更优。
#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
using namespace std;
typedef long long ll;
const int maxn = 2005, mod = 998244353;
template <typename T>
void read(T &x) {
T flag = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
x *= flag;
}
int N, d[maxn];
ll A[maxn];
priority_queue<pair<int, int>> q;
vector<int> vec[maxn], G[maxn];
bool vis[maxn];
void dfs(int u) { vis[u] = 1;
for (int v : vec[u])
!vis[v] && (G[u].pb(v), d[v]++, dfs(v), 1);
}
int main() {
read(N);
for (int i = 1; i <= N; i++) read(A[i]);
sort(A + 1, A + 1 + N);
for (int i = 1; i <= N; i++)
for (int j = i + 1; j <= N; j++)
if (__gcd(A[i], A[j]) > 1)
vec[i].pb(j), vec[j].pb(i);
for (int i = 1; i <= N; i++) !vis[i] && (dfs(i), 1);
for (int i = 1; i <= N; i++) d[i] == 0 && (q.push(mp(A[i], i)), 1);
while (!q.empty()) {
int u = q.top().second; q.pop();
printf("%lld ", A[u]);
for (int v : G[u])
d[v]--, d[v] == 0 && (q.push(mp(A[v], v)), 1);
}
return 0;
}
CF542E
考虑一个图如果有一个奇环,那么手玩一下发现是无解。然后考虑分层,于是答案就是最远点对的距离。
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int maxn = 1005;
template <typename T>
void read(T &x) {
T flag = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 +ch - '0';
x *= flag;
}
int n, m, col[maxn], bel[maxn], cnt, mx[maxn], ans, d[maxn];
queue<int> q;
vector<int> vec[maxn];
bool dfs(int u, int c) {
col[u] = c; bool ret = 1; bel[u] = cnt;
for (int v : vec[u]) col[v] == -1 ? ret &= dfs(v, c ^ 1) : ret &= col[v] != col[u];
return ret;
}
int bfs(int s) {
memset(d, -1, sizeof(d));
d[s] = 0; q.push(s); int ret = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
ret = max(ret, d[u]);
for (int v : vec[u]) d[v] == -1 && (d[v] = d[u] + 1, q.push(v), 1);
}
return ret;
}
int main() {
read(n); read(m); for (int i = 1; i <= n; i++) col[i] = -1;
for (int i = 1, u, v; i <= m; i++) {
read(u); read(v);
vec[u].pb(v); vec[v].pb(u);
}
for (int i = 1; i <= n; i++) {
if (col[i] == -1) {
cnt++;
if (!dfs(i, 0)) return puts("-1"), 0;
}
}
for (int i = 1; i <= n; i++) mx[bel[i]] = max(mx[bel[i]], bfs(i));
for (int i = 1; i <= cnt; i++) ans += mx[i];
printf("%d\n", ans);
return 0;
}
CF1458E
先不考虑特殊点,考虑把两堆石子的状态看成坐标轴上的点,那么每一行每一列只有一个非法状态,考虑把所有非法状态处理出来,用意发现这是一条 \(45\) 度的斜线。考虑它碰到了一个特殊点,假设这个特殊点在他左边,也就是说它从下面撞上了这个特殊点引出来的线平行于 x 轴的那一条,这相当于对其做了次向上偏移。我们考虑维护这个折线,有一种很好的写法是把截距相同的一段直接记在map里。具体实现可以参考代码(我也是参考别人的)
#include <bits/stdc++.h>
using namespace std;
const int inf=1e9;
const int MAXN=100005;
template <typename T>
void read(T &x) {
T flag=1;
char ch=getchar();
for (; '0'>ch||ch>'9'; ch=getchar()) if (ch=='-') flag=-1;
for (x=0; '0'<=ch&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
x*=flag;
}
struct node{
int x, y;
}p[MAXN];
int n, m;
map<int, int> mnx, mny;
map<pair<int, int>, bool> mark;
map<int, map<int, int> > mp;
int main() {
read(n); read(m);
for (int i=1, x, y; i<=n; i++) {
read(x); read(y);
mark[make_pair(x, y)]=true;
if (mnx.find(x)==mnx.end()) mnx[x]=y;
else mnx[x]=min(mnx[x], y);
if (mny.find(y)==mny.end()) mny[y]=x;
else mny[y]=min(mny[y], x);
}
int nowx=0, nowy=0;
for (; nowx<=inf&&nowy<=inf; ) {
if (mnx.find(nowx)!=mnx.end()&&mnx[nowx]<=nowy) {
nowx++;
continue;
}
if (mny.find(nowy)!=mny.end()&&mny[nowy]<=nowx) {
nowy++;
continue;
}
if (mnx.find(nowx)!=mnx.end()||mny.find(nowy)!=mny.end()) {
mark[make_pair(nowx, nowy)]=true;
nowx++;
nowy++;
continue;
}
map<int, int> :: iterator x=mnx.lower_bound(nowx), y=mny.lower_bound(nowy);
int tmp=inf;
if (x!=mnx.end()) tmp=min(tmp, (*x).first-nowx);
if (y!=mny.end()) tmp=min(tmp, (*y).first-nowy);
mark[make_pair(nowx, nowy)]=true;
mp[nowx-nowy][nowx+tmp]=nowx;
nowx+=tmp;
nowy+=tmp;
}
for (int i=1; i<=m; i++) {
int a, b;
read(a); read(b);
if (mark[make_pair(a, b)]) puts("LOSE");
else {
map<int, int> :: iterator it=mp[a-b].upper_bound(a);
if (it!=mp[a-b].end()&&(*it).second<=a) {
puts("LOSE");
} else {
puts("WIN");
}
}
}
return 0;
}//kel
CF1147F
这道题有一个关键是第一步不需要考虑前面的限制,那么我们猜想先手(B)不知道怎么输。考虑任意取出一组最大匹配中的两条匹配边 \((u,v),(p,q)\),不妨设先手选择的是 Increasing
,那么对于 \(w(v,p)>w(u,v)\) 有 \(w(p,q)<w(v,p)\) 那么这组匹配被称作好的。对于一组好的匹配,我们发现后手只要走匹配边即可。下面证明一定存在好的匹配,我们只需找出这组好的匹配即可。如果 \(\exists w(u,v)<w(v,p)<w(p,q)\),我们将这组匹配改为 \((p,v),(u,q)\) 即可变成一组好的,容易发现这就是我们在做稳定婚姻问题是更改匹配边的过程,所以我们对左侧点定义满意度为从小到大,右侧点为从大到小跑稳定婚姻问题即可。
#include <bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define F first
#define S second
const int maxn=105;
template<typename T>
void read(T &x){
T flag=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
x*=flag;
}
int n,a[maxn][maxn],rnk[maxn][maxn],pos[maxn],nxt[maxn],p;
std::priority_queue<pii>pq;
std::queue<int>q;
void solve(){
read(n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)read(a[i][j]);
putchar('B');putchar('\n');
fflush(stdout);
char ch;scanf("%c",&ch);
if(ch=='D')for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=-a[i][j];
read(p);
if(p>n)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=-a[i][j];
for(int i=1;i<=n;i++){
q.push(i);
for(int j=1;j<=n;j++)pq.push(mp(-a[i][j],j+n));
int tmp=0;
while(!pq.empty()){
int u=pq.top().S;pq.pop();
rnk[i][++tmp]=u;
}
}
for(int i=1;i<=n+n;i++)nxt[i]=0;
while(!q.empty()){
int u=q.front();q.pop();
if(!u)continue;
for(int i=1;i<=n&&!nxt[u];i++){
int w=rnk[u][i];
if(!nxt[w]||a[u][w-n]>a[nxt[w]][w-n])nxt[nxt[w]]=0,q.push(nxt[w]),nxt[u]=w,nxt[w]=u;
}
}
while(1){
if(p==-1||p==-2)break;
printf("%d\n",nxt[p]);fflush(stdout);
read(p);
}
}
int main(){
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}
CF1033
这道题得发现一个看起来很套路但又不太敢相信它是对的的套路。设当前局面为 \(S\),定义 \(S'\) 为每个 \(v'\to v \bmod (a+b)\) 后的局面,则 \(S\) 和 \(S'\) 的赢家是同样的人。
证明咕。
然后考虑计数,当 A 或 B 必胜时只与 \((a,b)\) 有关,所以令 \(a'=b,b'=a\) 那么胜负关系就反转了,所以 A 胜的方案一定与 B 胜的方案相同,所以我们只需要求出先手胜和后手胜的方案。注意每个 \(v'_i<a+b\)。
#include<bits/stdc++.h>
#define pb push_back
#define mp std::make_pair
#define pii std::pair<int,int>
#define F first
#define S second
typedef long long ll;
typedef unsigned long long ull;
namespace _name{
const int maxn=105,mod=998244353,inf=0x3f3f3f3f;
template<typename T>
inline void read(T &x){
T flag=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
x*=flag;
}
template<typename T>
inline void write(T x){
if(x==0)return putchar('0'),void();
int stk[20],top=0;
while(x)stk[++top]=x%10,x/=10;
for(int i=top;i;i--)putchar(stk[i]+'0');
}
template<typename T>
inline void print(T x,char End='\n'){
if(x<0)x=-x,putchar('-');
write(x);putchar(End);
}
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int ksm(int a,int b=mod-2){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
}using namespace _name;
int n;
ll m,v[maxn],w[maxn],ans[2];
int main(){
read(n);read(m);for(int i=1;i<=n;i++)read(v[i]);
for(int s=2;s<=2*m;s++){
for(int i=1;i<=n;i++)w[i]=v[i]%s;
std::sort(w+1,w+1+n,[](const int a,const int b){return a>b;});
w[0]=s-1;
for(int i=0,id=0;i<=n;i++,id^=1){
int l=std::max(w[i+1],w[id+1]/2)+1,r=std::min(m,w[i]);
ans[id]+=std::max(0,std::min(r,s-l)-std::max(l,s-r)+1);
}
}
print((1ll*m*m-ans[0]-ans[1])/2,' ');print((1ll*m*m-ans[0]-ans[1])/2,' ');print(ans[1],' ');print(ans[0]);
return 0;
}
AGC010D
如果存在一个 \(1\) 那么只用看其他数的和的奇偶性。否则如果没有偶数那么后手只要模仿先手即可,先手必败。所以如果只有一个偶数那么先手必胜。如果有奇数个偶数,注意到不可能全是偶数(因为一开始的 \(\gcd\) 是 \(1\)),那么先手可以保证一直都是奇数个偶数,最后一定会变成 \(1\) 个偶数,先手必胜。考虑偶数的偶数,因为先手想赢,他只有一种办法,那就是只有一个奇数,然后给这个奇数-1,剩下全除以 \(\gcd\),这种情况直接递归去做即可。
#include <bits/stdc++.h>
using namespace std;
int n;
int a[100005], cnt;
long long sum;
int ans;
void work(int id) {
sum=0;
cnt=0;
for (int i=1; i<=n; i++) {
if (a[i]==1) {
for (int j=1; j<=n; j++) sum+=a[j]-1;
if(sum&1) ans=id;
else ans=1-id;
return;
}
if(!(a[i]&1)) cnt++;
}
if (cnt&1) ans=id;
else {
if (n-cnt>1) ans=1-id;
else {
int g=0;
for (int i=1; i<=n; i++) if (a[i]&1) a[i]--;
for (int i=1; i<=n; i++) g=__gcd(g, a[i]);
for (int i=1; i<=n; i++) a[i]/=g;
work(1-id);
}
}
}
int main() {
scanf("%d", &n);
for (int i=1; i<=n; i++) scanf("%d", a+i);
work(1);
if (ans) puts("First");
else puts("Second");
return 0;
}
AGC026F
对于 \(n\) 为偶数的情况,通过反证法可以得知一定选左右开始。
对于奇数情况,由上可知一定选一个偶数的位置,然后后手会得到主动权然后递归去做,最后先手选了奇数的一段
考虑先全部选偶数,然后把一段改为奇数,这可以通过前缀和 \(+s[r]-s[l-1]\) 实现。
直接二分答案,然后求出合法区间,要求这些合法区间必须首位相连,即 \(r_{i}+1=l_{i+1}-1\)
我们可以使用一个 dp 判断,设 \(dp_{i}\) 代表 [1,i] 有没有合法区间划分,我们只需找到一个 \(dp_{j}=1\) 且 \(s[i-1]-s[j]>=mid\)
然后发现可以优化直接取最小的 \(s[j]\) 即可,复杂度 \(O(n\log{n})\)
#include <bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define F first
#define S second
const int maxn=300005;
template<typename T>
void read(T &x){
T flag=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
x*=flag;
}
int n,w[maxn],s[maxn],zz[2];
bool check(int m){
int mn=0;//dp[0]
for(int i=2;i<=n;i+=2)if(s[i-1]-mn>=m)mn=std::min(mn,s[i]);
return s[n]-mn>=m;
}
int main(){
read(n);for(int i=1;i<=n;i++)read(w[i]),zz[i&1]+=w[i];
if(!(n&1)){
printf("%d %d\n",std::max(zz[0],zz[1]),std::min(zz[0],zz[1]));
}else{
for(int i=1;i<=n;i++)s[i]=s[i-1]+(i&1?w[i]:-w[i]);
int l=0,r=zz[0]+zz[1],res=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))l=mid+1,res=mid;
else r=mid-1;
}
printf("%d %d\n",zz[0]+res,zz[1]-res);
}
return 0;
}