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('
');
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
",nxt[p]);fflush(stdout);
read(p);
}
}
int main(){
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}
CF1033
这道题得发现一个看起来很套路但又不太敢相信它是对的的套路。设当前局面为 (S),定义 (S') 为每个 (v' o v mod (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='
'){
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(nlog{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
",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
",zz[0]+res,zz[1]-res);
}
return 0;
}