CSP-S2考前综合强化刷题(第一场)
屑
A
考虑必胜策略的构造
假设我有最后一张牌
如果对方选取i,我就选择i+1
然后考虑我们这样做一定能有一个牌权
如果对方先手而且我的牌不比他多就能靠这个牌权取胜
所以:
奇数+奇先手必胜
奇数+偶先手必胜
偶数 偶必胜
n=2先手必胜
code:
#include<bits/stdc++.h>
using namespace std;
string s;
int T;
int main() {
scanf("%d",&T);
while(T-->0) {
int op;
cin>>s;
scanf("%d",&op);
if(s.size()==1 && s[s.size()-1]=='2') {
printf("%d\n",op);
} else if((s[s.size()-1]-'0')%2==0)puts("1");
else {
if(op==0)puts("0");
else puts("1");
}
}
return 0;
}
B
发现我们可以在线/se
举个例子:
染色一行,那么我们如果染蓝色,看看这一行最近一次染得是蓝色还是红色
如果染的蓝色,那么我们查询这个上次到这次时间区域染列红色的个数,那些会变蓝,对应加上
如果染的是红色,那么我们查询这次到上次时域染的行蓝色的个数,那些会不变,加上
直接暴力分类讨论要8中,所以我在代码中压缩了行列的区别
考场心态炸裂QAQ
不该打grakn forces2020
齐神的O(n)做法
每一行每一列按照染色的最后时间排序
然后就会发现上述过程可以排序双指针了....
code:
#include<bits/stdc++.h>
#define ll long long
const int MAXN=1e6+7;
using namespace std;
int n,m,k;
ll ans;
int col[2][MAXN],tim[2][MAXN];
//color time
const int MAXT=2e6+7;
struct rec {
#define lowbit(x) (x&(-x))
int tr[MAXT];
inline void mdf(int x,int v) {
for(; x<=k; x+=lowbit(x))tr[x]+=v;
}
inline ll qry(int x) {
ll ret=0;
for(; x; x-=lowbit(x))ret+=tr[x];
return ret;
}
} tr[2][2];//first h,l second r,b
namespace fastIO {
#define BUF_SIZE (1<<21)
static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = BUF_SIZE + buf;
inline char nc() {
if(p1 == pend) {
p1 = buf;
pend = buf + fread(buf, 1, BUF_SIZE, stdin);
}
return *p1++;
}
inline int read() {
int x = 0;
char s = nc();
for(; !isdigit(s); s = nc());
for(; isdigit(s); s = nc())x = (x << 1) + (x << 3) + s - '0';
return x;
}
}
using namespace fastIO;
int main() {
n=read();
m=read();
k=read();
ans=1ll*n*m;
for(int i=1,x,y,z; i<=k; ++i) {
x=read();
z=read();
y=read();
if(y==0) {//红色
if(col[x][z]==0) {//base red paint red
ans += tr[x^1][1].qry(i)-tr[x^1][1].qry(tim[x][z]);
} else {//base blue paint ret
if(x==1) {
ans += (n-tr[x^1][0].qry(i)+tr[x^1][0].qry(tim[x][z]));
} else ans += (m-tr[x^1][0].qry(i)+tr[x^1][0].qry(tim[x][z]));
}
} else {//蓝色
if(col[x][z]==1) {
ans -= tr[x^1][0].qry(i)-tr[x^1][0].qry(tim[x][z]);
} else {//红色??
if(x==0)
ans -= (m-tr[x^1][1].qry(i)+tr[x^1][1].qry(tim[x][z]));
else ans -= (n-tr[x^1][1].qry(i)+tr[x^1][1].qry(tim[x][z]));
}
}
if(tim[x][z]!=0) {
tr[x][col[x][z]].mdf(tim[x][z],-1);
}
tr[x][y].mdf(i,1);
col[x][z]=y;
tim[x][z]=i;
}
ans=1ll*n*m-ans;
printf("%lld\n",ans);
return 0;
}
C
nth_element
QAQ降智了
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=12345;
int n,Q,a[MAXN],v[MAXN];
struct rec {
ll W;
int id;
bool operator<(const rec &x)const {
return W==x.W?id<x.id:W>x.W;
}
} p[MAXN];
int t,k;
int main() {
scanf("%d",&n);
for(int i=0; i<n; ++i)scanf("%d%d",&v[i],&a[i]);
scanf("%d",&Q);
for(int i=1; i<=Q; ++i) {
scanf("%d%d",&t,&k);
for(int i=0; i<n; ++i) {
p[i].W=1ll*a[i]+1ll*v[i]*t;
p[i].id=i+1;
}
nth_element(p,p+k-1,p+n);
printf("%d\n",p[k-1].id);
}
return 0;
}
D
考虑DP,设表示看了前i个A数前j个B数能够得到的最小划分代价
然后转移是的,因为要枚举上一行上一列是什么
然后转移能优化吗QAQ?
你会发现:
结论:存在一个最优解的每次删数,至少有一段长度是1
因为我们有一段都大于1的
我们可以拆成和
然后
所以整完了,转移式子变得可以优化
实现的时候有一维可以直接前缀最小值
另一维...好像按理说也是可以的,不过我的实现是使用数组
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 2005;
int n, m;
int A[MAXN], B[MAXN];
ll f[MAXN][MAXN], suma[MAXN], sumb[MAXN];
ll minxa[MAXN][MAXN], minxb[MAXN];
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d", &A[i]);
A[i]--;
suma[i] = suma[i - 1] + A[i];
}
for(int i = 1; i <= m; ++i) {
scanf("%d", &B[i]);
B[i]--;
sumb[i] = sumb[i - 1] + B[i];
}
if(n < m) {
swap(n, m);
swap(A, B);
swap(suma, sumb);
}
memset(f, 0x3f3f3f3f, sizeof(f));
memset(minxa, 0x3f3f3f3f, sizeof(minxa));
memset(minxb, 0x3f3f3f3f, sizeof(minxb));
minxa[0][0] = 0;
for(int i = 1; i <= m; ++i)minxa[0][i] = 0;
minxb[0] = 0;
f[0][0] = 0;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
f[i][j] = min(f[i][j], minxa[i - 1][j - 1] + A[i] * sumb[j]);
f[i][j] = min(f[i][j], minxb[j - 1] + B[j] * suma[i]);
}
for(int j = 1; j <= m; ++j) {
minxa[i][j] = min(minxa[i][j - 1], f[i][j] - A[i + 1] * sumb[j]);
minxb[j] = min(minxb[j], f[i][j] - B[j + 1] * suma[i]);
}
}
printf("%lld\n", f[n][m]);
return 0;
}
/*
3 4
2 4 3
2 6 6 4
*/