【CF】2021十月CF刷题之旅
想了想感觉每次CF都发一篇文章挺浪费的,就把一个月的刷题都总结在这里。想了想,就算不搞ACM,还是尽量把自己cf分保持在1900+能保持算法状态(也有可能是痴心妄想hhhh)
【CF】 Educational Codeforces Round 115 (Rated for Div. 2)
D
如果直接选就是C(n,3),然后考虑限制条件,可以发现不满足的三元对为(Xa,Ya),(Xa,Yb),(Xb,Yb)然后我们发现三元组中中间那个特别,其余的两个都分别有一个变量与之相同。那么最后找不满足的三元对时,我们枚举中间那个,对于它为中间不满足的就是(cntx[Xa]-1) * (cnty[Yb]-1),关于cnt预统计一下即可。
点击查看代码
typedef long long ll;
ll n;
int cta[200005],ctb[200005];
int aa[200005],bb[200005];
ll ans;
void sol() {
scanf("%lld",&n);
for(int i=1;i<=n;i++) ctb[i]=cta[i] = 0;
for(int i=1;i<=n;i++) {
int a,b;
scanf("%d%d",&a,&b);
aa[i] = a; bb[i] = b;
cta[a]++; ctb[b]++;
}
ans = 1ll * n * (n-1) * (n-2)/6ll;
for(int i=1;i<=n;i++) {
ans -= 1ll*(cta[aa[i]]-1) * (ctb[bb[i]]-1);
}
printf("%lld\n",ans);
}
int main(){
int t;
scanf("%d",&t);
while(t--) {
sol();
}
}
E
设f[x][y][down/right]表示方案数,最后一个下来是往下转下来还是往右转下来的。有f[x][y][down]=f[x-1][y][right]+1 , f[x][y][right]=f[x][y-1][down]+1,那么可以O(nm)计算出初始的总方案数(注意直接所有方案数加起来把单格子计算了两次),之后每次修改,因为我们发现对于一个状态转移是唯一的,并且发现每次修改影响的格子并不多,那么我们每次暴力更新一下DP,O(nq)就可以了。
点击查看代码
点击查看代码
typedef long long ll;
const int maxn = 1005;
int n,m,q;
ll f[maxn][maxn][2]; //0 down 1 right 是如何到(x,y)
bool blo[maxn][maxn];
ll ANS,freecnt;
void dfs(int x,int y,int did) {
if(blo[x][y]||x>n||y>m) return;
ANS -= f[x][y][did];
if(did==1) {
f[x][y][1] = f[x][y-1][0] + 1;
dfs(x+1,y,0);
}
else {
f[x][y][0] = f[x-1][y][1] + 1;
dfs(x,y+1,1);
}
ANS += f[x][y][did];
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
f[i][j][0] = f[i-1][j][1] + 1;
f[i][j][1] = f[i][j-1][0] + 1;
ANS += f[i][j][0] + f[i][j][1];
}
}
freecnt = 1ll*n*m;
while (q--) {
int x,y;
scanf("%d%d",&x,&y);
if(blo[x][y]){
blo[x][y] ^= 1;
f[x][y][0] = f[x-1][y][1]+1;
f[x][y][1] = f[x][y-1][0]+1;
ANS+=f[x][y][0]+f[x][y][1];
dfs(x+1,y,0);
dfs(x,y+1,1);
freecnt++;
}
else {
blo[x][y] ^= 1;
ANS -= f[x][y][0] + f[x][y][1];
f[x][y][0] = f[x][y][1] = 0;
dfs(x+1,y,0);
dfs(x,y+1,1);
freecnt--;
}
printf("%lld\n",ANS-freecnt);
}
return 0;
}
Codeforces Round #745 (Div. 1)
A
突然发现出题者是我母校的学弟大佬,%%%
没做出来,本题基本参考
仔细分析发现这好像就是求O(N^3)最大二维前缀和?枚举上界,下界,然后枚举r,顺便更新最优前缀和,来避免O(n^4)枚举。。。还是太菜了。
点击查看代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>
using namespace std;
const int maxn = 405;
int n,m;
char ss[maxn];
int a[maxn][maxn],sm[maxn][maxn];
int gsum(int xa,int ya,int xb,int yb) {
return sm[xb][yb] - sm[xa-1][yb] - sm[xb][ya-1] + sm[xa-1][ya-1];
}
int ANS;
int hanbao(int xa,int ya,int xb,int yb) {
return gsum(xa+1,ya,xb-1,yb) + (yb-ya+1)*2-gsum(xa,ya,xa,yb)-gsum(xb,ya,xb,yb);
}
int clac(int up,int dw,int i) {
return dw-up-1-gsum(up+1,i,dw-1,i);
}
void sol() {
scanf("%d%d",&n,&m);
ANS = n*m;
for(int i=1;i<=n;i++) {
scanf("%s",&ss[1]);
for(int j=1;j<=m;j++) {
a[i][j] = ss[j]-'0';
sm[i][j] = sm[i-1][j]+sm[i][j-1]-sm[i-1][j-1]+a[i][j];
}
}
for(int up=1;up<=n;up++) {
for(int dw=up+4;dw<=n;dw++) {
int maxpre = -n*m;
for(int i=4;i<=m;i++) {
maxpre = max(maxpre,hanbao(up,1,dw,i-3)-clac(up,dw,i-3));
ANS = min(ANS,hanbao(up,1,dw,i-1)+clac(up,dw,i)-maxpre);
}
}
}
printf("%d\n",ANS);
}
int main(){
int t;
scanf("%d",&t);
while(t--) sol();
return 0;
}
Codeforces Round #748 (Div. 3)
div3都打不明白,大概是真的废了。
D2
做法有两个。。一个是n^5(实际完全打不到),一个是2e6*n。
第一个做法就是设背包s[i][k]表示从1到i,取了k个数,包含的所有公共gcd,(把它们全部放在set里),然后每次扩展往后取就可以了。这样我们就可以从s[x][n/2]中找到满足的最大答案
第二个做法。。直接爆枚所有的mod,然后O(n)验证。。。。(考场上真是傻子没想到cxxx)。
法一
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>
using namespace std;
int gcd(int a,int b) {
return (!b)?a:gcd(b,a%b);
}
int n;
int a[45];
set<int>se[45][45];
void sol() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
se[0][0].insert(0);
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) se[i][j].clear();
}
for(int i=1;i<=n;i++) {
for(int j=0;j<=i-1;j++) {
for(int k=1;k<=i;k++) {
for(auto it:se[j][k-1]) {
int oo;
if(j==0) oo = 0;
else oo = gcd(a[i]-a[j],it);
if(!se[i][k].count(oo))
se[i][k].insert(oo);
}
}
}
}
int ANS = 1;
for(int i=n/2;i<=n;i++) {
if(!se[i][n/2].empty()) {
if(*se[i][n/2].begin()==0) {
puts("-1"); return;
}
ANS = max(ANS,*se[i][n/2].rbegin());
}
}
printf("%d\n",ANS);
}
int main() {
int t;
scanf("%d",&t);
while(t--) sol();
}
2
法二
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>
using namespace std;
const int MM = 2000005;
int n;
int a[45],b[45]; int cnt[MM];
void sol() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int mod = MM;mod>=1;mod--) {
int bst = 0;
for(int i=1;i<=n;i++) {
b[i] = a[i]%mod;
if(b[i]<0) b[i]+=mod;
cnt[b[i]]++;
bst = max(bst,cnt[b[i]]);
}
for(int i=1;i<=n;i++) cnt[b[i]]--;
if(bst>=n/2) {
if(mod==MM) puts("-1");
else printf("%d\n",mod);
return;
}
}
}
int main(){
int t;
scanf("%d",&t);
while (t--) {
sol();
}
return 0;
}
F
这么简单的数位DP我都已经做不了了。。。没救了。。。
直接用dp[i][j][a][b]表示考虑到第i位,已经选取了其中j个为red,当前的红数%A===a,黑树%B==b,对应的方案(long long 二进制表示RB选取情况)
点击查看代码
using namespace std;
int n,A,B;
long long dp[45][45][45][45]; // 第几个 R多少 a b
//solution 0 R 1 B
char ss[45];
void sol() {
scanf("%d%d%d",&n,&A,&B);
memset(dp,-1,sizeof(dp));
dp[0][0][0][0] = 0;
scanf("%s",&ss[0]);
for(int i=0;i<n;i++) {
for(int j=0;j<=i;j++) {
for(int a=0;a<A;a++) {
for(int b=0;b<B;b++) {
if(dp[i][j][a][b]==-1) continue;
int o = ss[i]-'0';
dp[i+1][j+1][(a*10+o)%A][b] = dp[i][j][a][b];
dp[i+1][j][a][(b*10+o)%B] = dp[i][j][a][b] + (1ll<<i);
}
}
}
}
long long fa = -1;
int ans = 2*n;
for(int i=1;i<n;i++) {
if(dp[n][i][0][0]!=-1) {
if(abs(n-2*i)<ans) {
ans = abs(n-2*i);
fa = dp[n][i][0][0];
}
}
}
if(fa==-1) puts("-1");
else {
for(int i=0;i<n;i++) {
putchar( ((fa>>i)&1)?'B':'R' );
}
puts("");
}
}
int main(){
int t;scanf("%d",&t);
while(t--) sol();
return 0;
}
G
开始很蠢地写了一个莫队没玄学过去。
看了题解:
.....我是个傻子
点击查看代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>
using namespace std;
const int maxn = 1000005;
int n,q;
char ss[maxn];
int sm[maxn];
void sol() {
scanf("%s",&ss[1]);
n = strlen(ss+1);
for(int i=1;i<=n;i++) {
sm[i] = sm[i-1];
if(ss[i]==']'||ss[i]=='[') sm[i] += (i%2?1:-1);
}
scanf("%d",&q);
while(q--) {
int l,r; scanf("%d%d",&l,&r);
printf("%d\n",abs(sm[r]-sm[l-1]));
}
}
int main(){
int t;
scanf("%d",&t);
while(t--) sol();
return 0;
}
F
大致思路就是对于任意的一个bitmask组合他的左右括号差是固定的,每次转移,往后加入,如果满足转移后左括号大于右括号可转移,否则不能(因为如果右大于左不能再往后转移),同时更新无论可否转移更新答案,转移所需要的预处理,,具体题解中的dalao写得很清楚了。
1592D
cf的构造题最多的有三种:1.二分 2.分组分块 3 二进制分组。
这个可以想到二分,但是在树上我们可以想到转dfs序。但是我们需要的考虑边没考虑进去,两个由边连在一起的点可能不能最后二分到一起。那么我们考虑到欧拉序。神奇的欧拉序将每一条边都考虑进去,这意味着最后我们能二分到的两个点一定是相连的。
点击查看代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<bitset>
#include<queue>
#include<vector>
#include<cstdio>
#include<set>
#include<map>
#include<cmath>
#include<random>
#include<ctime>
#include<complex>
#include<iomanip>
using namespace std;
const int maxn = 2e3+5;
vector<int>ve[maxn],qv;
int n;
int oula[maxn],oid;
int query(){
sort(qv.begin(),qv.end());
vector<int>::iterator eit = unique(qv.begin(),qv.end());
qv.erase(eit,qv.end());
cout<<"? "<<qv.size()<<' ';
for(auto x:qv) cout<<x<<" ";
cout<<endl;
fflush(stdout);
int x;
scanf("%d",&x);
return x;
}
void dfs(int x,int pre){
for(auto y:ve[x]) {
if(y==pre) continue;
oula[++oid] = y;
dfs(y,x);
oula[++oid] = x;
}
}
int main(){
cin>>n;
for(int i=1;i<n;i++) {
int x,y;
cin>>x>>y;
ve[x].push_back(y);
ve[y].push_back(x);
}
oula[++oid] = 1;
dfs(1,0);
int L = 1 , R = oid , mid;
for(int i=1;i<=n;i++) qv.push_back(i);
int ANS = query();
while(L+1<R) {
mid = (L+R)>>1;
qv.clear();
for(int i=L;i<=mid;i++) {
qv.push_back(oula[i]);
}
int nans = query();
if(nans==ANS) R = mid;
else L = mid;
}
cout<<"! "<<oula[L]<<' '<<oula[R];
return 0;
}
1594E2
又是一道将dp的部分单独抽出来做的题。。。对于二叉树K层那么我们这n个点单独抽出来也就n*K个点,之后随便跑跑DP就可以了。
991E
背包+组合,一个数位上的数考虑为一个物品,DP[n][S]表示考虑到了第n个数字符号,已经填充了S个数的方案数,转移就考虑插进去一个新数位数。
CF749C
开始傻呗写了个扫描线的方法,结果发现直接区间前缀和就可以,甚至可以解决是区间的问题。