2021年11月的做题记录

Posted KinuhataSaiai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021年11月的做题记录相关的知识,希望对你有一定的参考价值。

杂题选做

最后一个月的做题记录。从NOIP开始,在NOIP结束。

P1063 [NOIP2006 提高组] 能量项链

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1005;
int n,l[N],r[N],dp[N][N],ans;
int main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for (int i=1; i<=n; i++) {
		cin>>l[i];
		l[i+n]=l[i];
		r[i-1]=l[i];
		r[i-1+n]=r[i-1];
	}
	r[2*n]=l[1];
	for (int len=2; len<=n; len++)
		for (int j=1; j+len-1<=2*n; j++) {
			int t=j+len-1;
			for (int k=j; k<=t-1; k++)
				dp[j][t]=max(dp[j][t],dp[j][k]+dp[k+1][t]+l[j]*r[k]*r[t]);
			if (len==n)
			  ans=max(ans,dp[j][t]);
		}
	cout<<ans;
}

P3554 [POI2013]LUK-Triumphal arch

给一颗 n 个节点的树(\\(n \\le 3 \\times 10^5\\)),初始时 1 号节点被染黑,其余是白的。两个人轮流操作,一开始 B 在 1 号节点。每一轮,A 选择 k 个点染黑,然后 B 走到一个相邻节点,如果 B 当前处于白点则 B 胜,否则当 A 将所有点染为黑点时 A 胜。求能让 A 获胜的最小的 k 。

考虑二分k,对于每一个k进行树形dp,保存每个结点的需求-供给,多余的可以向上传递,最后判断根节点正负即可。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=500005,M=500005;
struct edge{
	int to,next;
} e[M];
int n,cnt,head[N],rin[N],maxv=-1,l,r,f[N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int now,int bef,int k){
	f[now]=rin[now]-k;
	for (int i=head[now];i;i=e[i].next){
		int v=e[i].to;
		if (v==bef) continue;
		dfs(v,now,k);
		f[now]+=max(0,f[v]);
	}	
}
bool check(int k){
	dfs(1,0,k);
	if (f[1]<=0) return true;//!!!!!不是等于0!!!!! 
	else return false;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for (int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		add(u,v),add(v,u);
		rin[u]++,rin[v]++;
	}
	for (int i=2;i<=n;i++)
		rin[i]--;
	l=0,r=n;
	while (l<=r){
		int mid=(l+r)/2;
		if (check(mid)) r=mid-1;
		else l=mid+1;
	}
	cout<<l<<endl;
}

CF296B Yaroslav and Two Strings

阴间题,大力转移要转一页纸(不愧是CF).

一遍就A了(主要在于样例真是强),把dp数组小的维放前面就比rk2快一倍(再写个取模优化是不是要快114514倍

题意:给定两个由数字和问号组成的等长字符串\\(A,B\\),定义性质\\(P:\\exist 1 \\le i,j \\le len\\),满足\\(A_i < B_i \\and A_j > B_j\\),求将问号替换成数字使\\(A,B\\)满足性质\\(P\\)的方案数.

考虑序列dp,观察到性质是两方面的,一方面是令A中有数大于B,另一方面则是小于,这两方面显然不能在一次转移中完成,故在状态设计中加入两维,分别记录是否已满足这两方面性质,再加一维表示序列的前i个,即用\\(dp_{0/1,0/1,i}\\)存储方案数.

考虑转移,由于不可能一次满足两个性质,显然满足一个性质的要从不满足任何性质和满足同一性质转移过来,而满足两个性质的要从两种满足一个性质和已经满足两个性质的转移过来,不满足任何性质的只能从之前不满足任何性质的转移过来,同时注意根据问号数量讨论:若都不是问号,根据数字大小关系直接转移;若有一个问号,考虑在满足给定性质时问号的方案数,显然种类并不多,可以提前算出.易得转移方程:

\\[dp_{i,0,0}=\\left\\{ \\begin{array}{rcl} dp_{i-1,0,0},D(A_i) \\or D(B_i) \\\\ 10dp_{i-1,0,0},!D(A_i)\\and !D(B_i) \\end{array} \\right. \\\\ dp_{i,0,1}=\\left\\{ \\begin{array}{rcl} dp_{i-1,0,1},A_i=B_i\\\\ dp_{i-1,0,0}+dp_{i-1,0,1},A_i<B_i\\\\ (10-A_i)dp_{i-1,0,1}+(9-A_i)dp_{i-1,0,0},D(A_i)\\and !D(B_i)\\\\ (B_i+1)dp_{i-1,0,1}+B_idp_{i-1,0,0},!D(A_i)\\and D(B_i)\\\\ 45dp_{i-1,0,0}+55dp_{i-1,0,1},!D(A_i)\\and !D(B_i) \\end{array} \\right. \\\\ dp_{i,1,0}=\\left\\{ \\begin{array}{rcl} dp_{i-1,1,0},A_i=B_i\\\\ dp_{i-1,0,0}+dp_{i-1,1,0},A_i>B_i\\\\ (10-B_i)dp_{i-1,1,0}+(9-B_i)dp_{i-1,0,0},!D(A_i)\\and D(B_i)\\\\ (A_i+1)dp_{i-1,1,0}+A_idp_{i-1,0,0},D(A_i)\\and !D(B_i)\\\\ 45dp_{i-1,0,0}+55dp_{i-1,1,0},!D(A_i)\\and !D(B_i) \\end{array} \\right. \\\\ dp_{i,1,1}=\\left\\{ \\begin{array}{rcl} dp_{i-1,1,1},A_i=B_i\\\\ dp_{i-1,1,0}+dp_{i-1,0,1},A_i\\not =B_i\\\\ B_idp_{i-1,1,0}+(9-B_i)dp_{i-1,0,1}+10dp_{i-1,1,1},!D(A_i)\\and D(B_i)\\\\ (9-A_i)dp_{i-1,1,0}+A_idp_{i-1,0,1}+10dp_{i-1,1,1},D(A_i)\\and !D(B_i)\\\\ 45dp_{i-1,0,1}+45dp_{i-1,1,0}+100dp_{i-1,1,1},!D(A_i)\\and !D(B_i) \\end{array} \\right. \\]

//写转移时始终想着一次最多只能满足一方面性质就会好写很多
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100005,Mod=1000000007;
int n,a[N],b[N];
long long dp[2][2][N];
string s1,s2;
int main() {
    ios::sync_with_stdio(false);
    cin>>n>>s1>>s2;
    s1=\' \'+s1,s2=\' \'+s2;//让下标从1开始
    for (int i=1; i<=n; i++) {
        if (s1[i]==\'?\')a[i]=-1;//问号标为-1
        else a[i]=s1[i]-\'0\';
        if (s2[i]==\'?\')b[i]=-1;
        else b[i]=s2[i]-\'0\';
    }
    dp[0][0][0]=1;
    for (int i=1; i<=n; i++) {
        if (a[i]!=-1&&b[i]!=-1) {
            if (a[i]==b[i]) {//直接转移
                dp[0][0][i]=dp[0][0][i-1]%Mod;
                dp[0][1][i]=dp[0][1][i-1]%Mod;
                dp[1][0][i]=dp[1][0][i-1]%Mod;
                dp[1][1][i]=dp[1][1][i-1]%Mod;
            }
            if (a[i]>b[i]) {//只能满足一方面性质
                dp[1][0][i]=dp[0][0][i-1]%Mod+dp[1][0][i-1]%Mod;
                dp[1][1][i]=dp[0][1][i-1]%Mod+dp[1][1][i-1]%Mod;
            }
            if (a[i]<b[i]) {//另一方面
                dp[0][1][i]=dp[0][0][i-1]%Mod+dp[0][1][i-1]%Mod;
                dp[1][1][i]=dp[1][0][i-1]%Mod+dp[1][1][i-1]%Mod;
            }
            continue;
        }
        if (a[i]!=-1&&b[i]==-1) {//单问号
            dp[0][0][i]=dp[0][0][i-1]%Mod;
            dp[0][1][i]=dp[0][1][i-1]*(10-a[i])%Mod+dp[0][0][i-1]*(9-a[i])%Mod;
            dp[1][0][i]=dp[1][0][i-1]*(a[i]+1)%Mod+dp[0][0][i-1]*a[i]%Mod;
            dp[1][1][i]=dp[1][0][i-1]*(9-a[i])%Mod+dp[0][1][i-1]*a[i]%Mod+dp[1][1][i-1]*10%Mod;
            continue;
        }
        if (a[i]==-1&&b[i]!=-1) {//单问号
            dp[0][0][i]=dp[0][0][i-1]%Mod;
            dp[0][1][i]=dp[0][1][i-1]*(b[i]+1)%Mod+dp[0][0][i-1]*b[i]%Mod;
            dp[1][0][i]=dp[1][0][i-1]*(10-b[i])%Mod+dp[0][0][i-1]*(9-b[i])%Mod;
            dp[1][1][i]=dp[1][0][i-1]*b[i]%Mod+dp[0][1][i-1]*(9-b[i])%Mod+dp[1][1][i-1]*10%Mod;
            continue;
        }
        if (a[i]==-1&&b[i]==-1) {//双问号
            dp[0][0][i]=dp[0][0][i-1]*10%Mod;
            dp[0][1][i]=dp[0][0][i-1]*45%Mod+dp[0][1][i-1]*55%Mod;
            dp[1][0][i]=dp[0][0][i-1]*45%Mod+dp[1][0][i-1]*55%Mod;
            dp[1][1][i]=dp[0][1][i-1]*45%Mod+dp[1][0][i-1]*45%Mod+dp[1][1][i-1]*100%Mod;
            continue;
        }
        cout<<"Fail"<<endl;
    }
    cout<<dp[1][1][n]%Mod<<endl;
}

@uptracer 写的更为巧妙:大大减小了转移的难度。

#include<bits/stdc++.h>

using namespace std;
using ll=long long;
const int N=100010,mod=1e9+7;
int n;
char s[N],w[N];
ll solve(char *s,char *w)// s[i]<=w[i]
{
    
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        if(s[i]==\'?\'&&w[i]==\'?\') ans=ans*55%mod;
        else if(s[i]==\'?\'&&w[i]!=\'?\') ans=ans*(w[i]-\'0\'+1)%mod;
        else if(s[i]!=\'?\'&&w[i]==\'?\') ans=ans*(10+\'0\'-s[i])%mod;
        else if(s[i]>w[i]) return 0;
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    
    cin>>n;
    cin>>s+1>>w+1;
    ll ans=1;
    for(int i=1;i<=n;i++) 
    {
        if(s[i]==\'?\') ans=ans*10%mod;
        if(w[i]==\'?\') ans=ans*10%mod;
    }
    ans=((ans-solve(s,w)-solve(w,s))%mod+mod)%mod;
    
    ll ret=1;
    
    for(int i=1;i<=n;i++)
    {
        if(s[i]==\'?\'&&w[i]==\'?\') ret=ret*10%mod;
        else if(s[i]!=w[i]&&(s[i]!=\'?\'&&w[i]!=\'?\')) ret=0;
    }
    //cout<<solve(s,w)<<\'\\n\'<<solve(w,s)<<\'\\n\'<<ret<<\'\\n\';
    ans=(ans+ret)%mod;
    cout<<ans<<\'\\n\';
    return 0;
}

CF44E Anfisa the Monkey

给定一个字符串,要求将它分为k行长度>=a且<=b的段,输出这些被划分出来的部分,没有符合要求的划分就输出"No solution"

做法多种,比较随意,看代码即可。

#include<iostream>
#include<cstdio>
using namespace std;
string s;
int k,a,b,head;
int main() {
	ios::sync_with_stdio(false);
	cin>>k>>a>>b;
	cin>>s;
	int l=s.length();
	if (l<a*k||l>b*k) {
		cout<<"No solution"<<endl;
		return 0;
	}
	for (int i=0; i<l%k; i++) {
		for (int j=0; j<=l/k; j++) 
			cout<<s[head++];
		cout<<endl;
	}
	for (int i=l%k; i<k; i++) {
		for (int j=0; j<l/k; j++) 
			cout<<s[head++];
		cout<<endl;
	}
}

P2986 [USACO10MAR]Great Cow Gathering G:

换根dp模板题.

题意:一棵树有点权和边权,求一个点使其他点到这个点的路径长乘以点权之和最小.解法:第一遍dfs求出以1为根节点的答案和每个子树的大小,第二次dfs换根,每次将移远根的和移近根用子树大小快速统计.转移方程:

\\[dp_{v}=dp_{u}-size_v*dis_{u,v}+(Sum-size_v)*dis_{u,v} \\]

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int N = 500005, M = 500005, INF = 1000000000000000000;
int n, head[N], cnt, tot, c[N], dis[N], ans = INF, siz[N], f[N], sum;
struct edge
{
    int to, nxt, dis;
} e[M];
void add(int u, int v, int d)
{
    e[++cnt].to = v;
    e[cnt].nxt = head[u];
    e[cnt].dis = d;
    head[u] = cnt;
}
void dfs1(int k, int bef, int d)
{
    siz[k] += c[k];
    for (int i = head[k]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if (v == bef)
            continue;
        dis[v] = c[v] * (d + e[i].dis);
        tot += dis[v];
        dfs1(v, k, d + e[i].dis);
        siz[k] += siz[v];
    }
}
void dfs2(int k, int bef)
{
    for (int i = head[k]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if (v == bef)
            continue;
        f[v] = f[k] - siz[v] * e[i].dis + (sum - siz[v]) * e[i].dis;
        dfs2(v, k);
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
        sum += c[i];
    }
    for (int i = 1; i <= n - 1; i++)
    {
        int u, v, d;
        cin >> u >> v >> d;
        add(u, v, d), add(v, u, d);
    }
    dfs1(1, 0, 0);
    f[1] = tot;
    dfs2(1, 0);
    for (int i = 1; i <= n; i++)
        ans = min(ans, f[i]);
    cout << ans << endl;
}

一个题单codeforces 2000左右的dp题

CF148E Porcelain(想出部分)

  • 好题,完美诠释dp圣经.题意:有n个由正数组成的序列,取m次,每次只能从两端取,使总和最大.直接两维dp难以着手,考虑一种想法:
  • 发现对于每一个序列,取固定次数能获得最大价值是固定的,显然可以先预处理出结果,然后问题转化为:
    • 1)有一个序列只能从两端取,求取k次的最大价值;容易发现LRLR和LLRR是等效的,即我们不关心取的顺序,只关心最终在左右分别取了多少个,先求出前缀和,然后枚举即可.
    • 2)有n组物品,每组物品只能选一个,物品有价值和体积,求在给定体积下的最大价值.这里的“一组物品”指的是将同一序列中取法的取的次数作为体积,取得的价值作为价值形成的一个组,显然这个组中物品只能选一个.运用分组背包模板即可解决.
  • 解决此题的难点在于需要发现在序列间选择和在序列内选择是独立的,从而防止无用的大量状态转移.
  • 然而WA飞了....因为每个序列不一样长,所以使用偷懒的后缀和就没了....以后还是至少清一下len+1位置的数组...
//VSCode的格式化怎么这么难看...要不是有某二次元主题早就换了
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 105, M = 10005;
int n, m;
int k[N], a[N], sum1[N], sum2[N], sub[N][N], dp[N][M];
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        memset(sum1, 0, sizeof(sum1));
        memset(sum2, 0, sizeof(sum2));
        cin >> k[i];
        for (int j = 1; j <= k[i]; j++)
            cin >> a[j];
        for (int j = 1; j <= k[i]; j++)
            sum1[j] = sum1[j - 1] + a[j];
        for (int j = k[i]; j >= 1; j--)
            sum2[j] = sum2[j + 1] + a[j];
        for (int t = 1; t <= k[i]; t++)
            for (int l = 0; l <= t; l++)
                sub[i][t] = max(sub[i][t], sum1[l] + sum2[k[i] - (t - l) + 1]);
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int t = 0; t <= min(j, k[i]); t++) //j要大于等于t
                dp[i][j] = max(dp[i][j], dp[i - 1][j - t] + sub[i][t]);
    cout << dp[n][m] << endl;
}

CF847E Packmen:(自己做出)

  • 奇怪的东西

  • 题意

    今有一区间,人,物具陈其上,间或亦有空.其人可左右徙于上.求让人的移动轨迹覆盖所有物品的情况下,人移动的路程的最大值的最小值.

  • 题解

    根据题意,显然看出是二分.再考虑一个人的走法:

    • 向左走,不回头

    • 向右走,不回头

    • 向左再向右,左边重复走

    • 向右再向左,右边重复走

若我们从左到右把人扫一遍,必然要保证先把左边的没走到的物品走到,同时在还有步数的情况下向右走.

这样会WA on #7 ,原因在于先向右走再向左走到第一个没走到的物品可能更优,因为重复的部分可能更少.

考虑分类讨论:用一个物品指针指向还没走到的最靠左的物品

  1. 步数不够到达最左边的没走到的物品\\(\\to\\)直接return false

  2. 步数只能到最左边的物品,无法向右走\\(\\to\\)把物品指针向右更新到人右边

  3. 步数很充足\\(\\to\\)在3,4两种走法中取最优解

  4. 指针在人右边\\(\\to\\)把物品指针尽可能向右更新

结束后判断指针是否大于\\(Cnt_{package}\\)即可

这样就又WA了,原因在于二分的右边界应当是\\(\\frac{3n}{2}\\),即只有一个人在正中间,先向某方向然后再折返的答案,程序里为了简单写成了\\(2n\\).

最终复杂度:\\(O(Cnt_{people}\\log2n)\\),明显快于\\(O(n\\log2n)\\).

  • 代码

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100005;
int n, L, R, cnt1, cnt2;
int peo[N], pack[N];
string s;
bool check(int k)
{
    int await_pick = 1;
    for (int i = 1; i <= cnt1; i++)
    {
        int start_pos = await_pick;
        if (await_pick > cnt2)
            return true;
        if (pack[start_pos] < peo[i])
        {
            if (pack[start_pos] < peo[i] - k)//第一种情况
                return false;
            if (k <= peo[i] - pack[start_pos] + 1)//第二种情况
            {
                while (pack[await_pick + 1] <= peo[i] && await_pick <= cnt2 && pack[await_pick + 1] <= peo[i + 1])
                    await_pick++;
                if (pack[await_pick] <= peo[i] && pack[await_pick] <= peo[i + 1])
                    await_pick++;
            }
            else
            {
                if (k > (2 * (peo[i] - pack[start_pos])))
                {//第三种情况,先向左走
                    int temp = k - (2 * (peo[i] - pack[start_pos]));
                    while (pack[await_pick + 1] <= peo[i] + temp && await_pick <= cnt2 && pack[await_pick + 1] <= peo[i + 1])
                        await_pick++;
                    if (pack[await_pick] <= peo[i] + temp && pack[await_pick] <= peo[i + 1])
                        await_pick++;
                }
                if (peo[i] + (k + pack[start_pos] - peo[i]) / 2 >= pack[await_pick])
                {//第三种情况,先向右走
                    int temp = (k + pack[start_pos] - peo[i]) / 2;
                    while (pack[await_pick + 1] <= peo[i] + temp && await_pick <= cnt2 && pack[await_pick + 1] <= peo[i + 1])
                        await_pick++;
                    if (pack[await_pick] <= peo[i] + temp && pack[await_pick] <= peo[i + 1])
                        await_pick++;
                }
            }
        }
        else
        {//第四种情况
            while (pack[await_pick + 1] <= peo[i] + k && await_pick <= cnt2 && pack[await_pick + 1] <= peo[i + 1])
                await_pick++;
            if (pack[await_pick] <= peo[i] + k && await_pick <= cnt2 && pack[await_pick] <= peo[i + 1])
                await_pick++;
        }
    }
    if (await_pick > cnt2)
        return true;
    else
        return false;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> s;
    for (int i = 0; i < s.length(); i++)
    {
        if (s[i] == \'P\')
            peo[++cnt1] = i + 1;
        if (s[i] == \'*\')
            pack[++cnt2] = i + 1;
    }
    peo[cnt1 + 1] = 0x7fffffff;
    pack[cnt2 + 1] = 0x7fffffff;
    L = 1, R = 2 * n;//注意二分边界
    while (L <= R)
    {
        int mid = (L + R) / 2;
        if (check(mid))
            R = mid - 1;
        else
            L = mid + 1;
    }
    cout << L;
}

CF1158C Permutation recovery(想到法1)

对于一个排列\\(a1...an\\), \\(next_i\\)表示排列中i后面第一个比\\(ai\\)大的位置.现给定一个排列的一部分\\(next_i\\)数组,试据此构造一个排列.
\\(next_i=n+1\\)表示i后面没有比它更大的数,\\(next_i=-1\\)表示不确定.\\(n \\leq 5*10^5\\)

法1: \\(O(nlogn)\\)

考虑定义,对于任意一个\\(nexti\\)不等于-1的位置i,有\\(ai>aj(i+1\\leq j \\leq nexti-1),a_{next_i} > a_i\\)成立,显然连边+拓扑排序,用线段树优化连边做到\\(O(nlogn)\\)即可.

法2: \\(O(n)\\)

  • Step1 考虑没有-1,且next互不相同怎么做.由于交叉必然无解,所以形成的区间是这样的:

容易发现,如果next互不相同,我们按照\\(next\\)从大到小排序依次填数,方案必定是合法的,原因在于,一个位置有比另一个位置小的限制,当且仅当被一个\\((i,next_i)\\)的区间覆盖,排序后就保证了右边的比左边小,所以必然合法.

  • Step2 考虑有\\(next\\)相同怎么做.显然,对于\\(next\\)相等的位置,越靠前,数越大,对于这些位置按照从小到大排序即可.

  • Step3考虑有-1怎么做.相当于在不考虑-1形成的区间中加入许多新区间,使其合法,如图:

容易发现,只要让i直接指向i+1就不会产生任何交叉区间.如图:

用一个vector存储某个位置作为\\(next\\)被哪些点指向,扫一遍,依次填数,就是一种合法方案.

P5025 [SNOI2017]炸弹(学习了新算法)

在数轴上给定n个炸弹,每个炸弹有位置和爆炸半径,如果爆炸半径内有别的炸弹,那么爆炸时就会引爆它们,依次求出从某个炸弹起爆会引起多少个炸弹爆炸.\\(n \\leq 5*10^5\\)

容易发现,本题与之前的CF1158C异曲同工.只是本题更为直观.

考虑二分查找出每个炸弹起爆能引发的爆炸区间,从该炸弹向引爆的炸弹连边,最后dfs过程中统计能到达的结点中最左和最右在哪即可.

由于n较大,连边复杂度过高,考虑线段树优化连边.显然连边之后,对于每个强联通分量,引爆其中任何一个都是引爆整个强联通分量,使用tarjan在线段树上缩点建新图,在新图上跑dfs即可.

思维难度不大,注意代码细节即可:

  • 初始化不能只到n,原因在于线段树缩点后点数可能依然多于原图(卡了半天).

  • 数字较大,注意long long 和取模.

  • dfs时注意一次性从前后更新所有经过的结点,而不是依次dfs,那样复杂度就boom了

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <map>
    #include <cstring>
    #include <assert.h>
    using namespace std;
    const long long N = 4 * 500005, INF = 0x3f3f3f3f3f3f3f3f, Mod = 1000000007;
    long long n, sum, tot, scc, top, ll, rr, totseg;
    long long head1[N], cnt1, head2[N], cnt2, loc[N], range[N], pos[N], dfn[N], low[N], stack[N], in_sta[N], bel[N], L[N], R[N], vis[N];
    map<long long, long long> m[N];
    struct point
    {
        long long l, r;
    } p[N * 4];
    struct edge
    {
        long long to, next;
    } e1[N * 4], e2[N * 4];
    void add1(long long u, long long v)
    {
        e1[++cnt1].to = v;
        e1[cnt1].next = head1[u];
        head1[u] = cnt1;
    }
    void add2(long long u, long long v)
    {
        e2[++cnt2].to = v;
        e2[cnt2].next = head2[u];
        head2[u] = cnt2;
    }
    void build(long long k, long long l, long long r)
    {
        p[k] = (point){l, r};
        totseg = max(totseg, k);
        if (l == r)
        {
            pos[l] = k;
            return;
        }
        long long mid = (l + r) / 2;
        build(k * 2, l, mid);
        build(k * 2 + 1, mid + 1, r);
        add1(k, k * 2);
        add1(k, k * 2 + 1);
    }
    void range_add(long long k, long long l, long long r, long long u)
    {
        if (ll <= l && rr >= r)
        {
            if (k == u)
                return;
            add1(u, k);
            return;
        }
        long long mid = (l + r) / 2;
        if (ll <= mid)
            range_add(k * 2, l, mid, u);
        if (rr > mid)
            range_add(k * 2 + 1, mid + 1, r, u);
    }
    void tarjan(long long k)
    {
        dfn[k] = low[k] = ++tot;
        stack[++top] = k;
        in_sta[k] = 1;
        for (long long i = head1[k]; i; i = e1[i].next)
        {
            long long v = e1[i].to;
            if (!dfn[v])
            {
                tarjan(v);
                low[k] = min(low[k], low[v]);
            }
            else if (in_sta[v])
                low[k] = min(low[k], dfn[v]);
        }
        if (low[k] == dfn[k])
        {
            scc++;
            while (stack[top + 1] != k)
            {
                bel[stack[top]] = scc;
                in_sta[stack[top]] = 0;
                L[scc] = min(L[scc], p[stack[top]].l);
                R[scc] = max(R[scc], p[stack[top]].r);
                top--;
            }
        }
    }
    void dfs(long long k)
    {
        vis[k] = 1;
        for (long long i = head2[k]; i; i = e2[i].next)
        {
            long long v = e2[i].to;
            if (vis[v])
            {
                L[k] = min(L[k], L[v]);
                R[k] = max(R[k], R[v]); //前驱也可以更新后继
                continue;
            }
            dfs(v);
            L[k] = min(L[k], L[v]);
            R[k] = min(R[k], R[v]); //后继更新前驱
        }
    }
    void build_new_graph()
    {
        for (long long i = 1; i <= totseg; i++)
            for (long long j = head1[i]; j; j = e1[j].next)
            {
                long long v = e1[j].to;
                if (bel[i] != bel[v] && !m[bel[i]][bel[v]])
                {
                    m[bel[i]][bel[v]] = 1;
                    add2(bel[i], bel[v]);
                }
            }
    }
    int main()
    {
        ios::sync_with_stdio(false);
        cin >> n;
        assert(n <= 500000);
        for (long long i = 1; i <= n; i++)
        {
            cin >> loc[i] >> range[i];
            L[i] = INF;
        }
        memset(L, 0x3f, sizeof(L));
        loc[n + 1] = INF;
        build(1, 1, n);
        for (long long i = 1; i <= n; i++)
        {
            if (!range[i])
                continue;
            ll = lower_bound(loc + 1, loc + 1 + n, loc[i] - range[i]) - loc;
            rr = upper_bound(loc + 1, loc + 1 + n, loc[i] + range[i]) - loc - 1;
            range_add(1, 1, n, pos[i]);
            p[pos[i]] = (point){ll, rr};
        }
        tarjan(1);
        build_new_graph();
        for (long long i = 1; i <= scc; i++)
            if (!vis[i])
                dfs(i);
        for (long long i = 1; i <= n; i++)
            sum = (sum + ((long long)(R[bel[pos[i]]] - L[bel[pos[i]]] + 1) % Mod * i) % Mod) % Mod;
        cout << sum % Mod << endl;
    }
    
    

「Wdoi2021」Windy OI Codeforces Round 1

T1 Beautiful Array

题意:试将一个长度为 n的括号串分为 2m个子序列,子序列可以为空,且每个字符都必须分到恰好一个子序列中。使得至少 m个子序列为匹配的括号串。空序列不算匹配的括号序列。无解请输出 0,否则输出 1。本题多组数据,其中数据组数为 T。定义 A为 B的子序列当且仅当 A 能由 B 在顺序不改变的前提下删除若干元素后得到。\\(1\\leq T,n,m \\leq 200\\)

签到题.由于数据非常小,暴力求出可能匹配的括号串与m比较即可.

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 250;
int T, n, m;
string s;
int vis[N];
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n >> m;
        cin >> s;
        s = \' \' + s;
        for (int i = 1; i <= n; i++)
            vis[i] = 0;
        int cnt = 0;
        for (int i = 1; i <= n; i++)
        {
            if (vis[i])
                continue;
            if (s[i] == \'(\')
                for (int j = i + 1; j <= n; j++)
                    if (s[j] == \')\' && !vis[j])
                    {
                        vis[i] = 1, vis[j] = 1, cnt++;
                        break;
                    }
            if (s[i] == \')\')
                for (int j = 1; j <= i - 1; j++)
                    if (s[j] == \'(\' && !vis[j])
                    {
                        vis[i] = 1, vis[j] = 1, cnt++;
                        break;
                    }
        }
        if (cnt >= m)
            cout << 1 << endl;
        else
            cout << 0 << endl;
    }
}

T2 Alice Wins! (easy version)

AB 每队 2n人正在玩石头剪刀布。A 队第 \\(i\\) 个人出$ a_i$,B 队第 \\(i\\) 人出 \\(b_i\\).编号相同的人会对战。若 A 队赢则加一分,平不得分,输扣一分。你可以至多改变每队 \\(n\\) 个人的出拳方案,使得 A 队的得分最高。输出得分的最大值和任意一组构造方案。

本题中,我们用 1代表石头,2代表剪刀,3代表布。

签到题.容易发现最坏情况也只需要一边各n次就能改成全赢,所以扫一遍直接改成全赢即可.注意不要让某一边改的次数超过n.

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 500005;
int n;
int cmp(int a, int b)
{
    if (a == b)
        return 0;
    if (a != 1 && b != 1)
    {
        if (a < b)
            return 1;
        if (a > b)
            return -1;
    }
    if (a == 1)
    {
        if (b == 2)
            return 1;
        if (b == 3)
            return -1;
    }
    if (b == 1)
    {
        if (a == 2)
            return -1;
        if (a == 3)
            return 1;
    }
}
int a[N], b[N];
int cnt;
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (int i = 1; i <= 2 * n; i++)
            cin >> a[i];
        for (int i = 1; i <= 2 * n; i++)
            cin >> b[i];
        cnt = 0;
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == -1 && cnt < n)
            {
                cnt++, a[i]++;
                if (a[i] == 4)
                    a[i] = 1;
            }
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == 0 && cnt < n)
            {
                cnt++, a[i]--;
                if (a[i] == 0)
                    a[i] = 3;
            }
        cnt = 0;
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == -1 && cnt < n)
            {
                cnt++, b[i]--;
                if (b[i] == 0)
                    b[i] = 3;
            }
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == 0 && cnt < n)
            {
                cnt++, b[i]++;
                if (b[i] == 4)
                    b[i] = 1;
            }
        int sum = 0;
        for (int i = 1; i <= 2 * n; i++)
            sum += cmp(a[i], b[i]);
        cout << sum << endl;
        for (int i = 1; i <= 2 * n; i++)
            cout << a[i] << " ";
        cout << endl;
        for (int i = 1; i <= 2 * n; i++)
            cout << b[i] << " ";
        cout << endl;
    }
}

T3 Alice Wins! (hard version)

题意:同T2,但改为每边改恰好n次.

略加思考容易发现,我们只需要解决改完全赢之后如何浪费次数就可以了.显然对于没改过的,即本来就赢的位置,可以上下同时+1/-1浪费两次操作,这样可能会因为奇偶剩下两边中某一边的一次修改,只需要将之前改成赢的的位置从改一边变成赢变成改两边变成赢即可.

码量较大细节较多,但没有太大难度.

#include <iostream>
#include <cstdio>
#include <assert.h>
using namespace std;
const int N = 500005;
int n;
int cmp(int a, int b)
{
    if (a == b)
        return 0;
    if (a != 1 && b != 1)
    {
        if (a < b)
            return 1;
        if (a > b)
            return -1;
    }
    if (a == 1)
    {
        if (b == 2)
            return 1;
        if (b == 3)
            return -1;
    }
    if (b == 1)
    {
        if (a == 2)
            return -1;
        if (a == 3)
            return 1;
    }
}
int a[N], b[N], arta[N], artb[N], ta[N], tb[N];
int cnta, cntb, cntfail, cntdraw;
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        cntdraw = 0, cntfail = 0, cnta = 0, cntb = 0;
        for (int i = 1; i <= 2 * n; i++)
            arta[i] = 0, artb[i] = 0;
        for (int i = 1; i <= 2 * n; i++)
        {
            cin >> a[i];
            ta[i] = a[i];
        }
        for (int i = 1; i <= 2 * n; i++)
        {
            cin >> b[i];
            tb[i] = b[i];
        }
        for (int i = 1; i <= 2 * n; i++)
        {
            if (cmp(a[i], b[i]) == -1)
                cntfail++;
            if (cmp(a[i], b[i]) == 0)
                cntdraw++;
        }
        if (cntfail % 2 == 1)
            cntfail++;
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == -1 && cnta < cntfail / 2)
            {
                cnta++;
                a[i]++;
                if (a[i] == 4)
                    a[i] = 1;
                arta[i] = 1;
            }
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == -1 && cntb < cntfail / 2)
            {
                cntb++;
                b[i]--;
                if (b[i] == 0)
                    b[i] = 3;
                artb[i] = 1;
            }
        int sa = cnta, sb = cntb;
        if (cntdraw % 2 == 1)
            cntdraw++;
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == 0 && cntb - sb < cntdraw / 2)
            {
                cntb++;
                b[i]++;
                if (b[i] == 4)
                    b[i] = 1;
                artb[i] = 1;
            }
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == 0 && cnta - sa < cntdraw / 2)
            {
                cnta++;
                a[i]--;
                if (a[i] == 0)
                    a[i] = 3;
                arta[i] = 1;
            }
        for (int i = 1; i <= 2 * n; i++)
            if (cmp(a[i], b[i]) == 1 && !(arta[i] || artb[i]) && cnta < n && cntb < n)
            {
                cnta++, cntb++;
                a[i]++, b[i]++;
                if (a[i] == 4)
                    a[i] = 1;
                if (b[i] == 4)
                    b[i] = 1;
            }
        if (cnta != n)
            for (int i = 1; i <= 2 * n; i++)
                if (cmp(a[i], b[i]) == 1 && artb[i] && cnta < n)
                {
                    cnta++;
                    a[i]++, b[i]++;
                    if (a[i] == 4)
                        a[i] = 1;
                    if (b[i] == 4)
                        b[i] = 1;
                    if (b[i] == tb[i])
                    {
                        a[i]++, b[i]++;
                        if (a[i] == 4)
                            a[i] = 1;
                        if (b[i] == 4)
                            b[i] = 1;
                    }
                    break;
                }
        if (cntb != n)
            for (int i = 1; i <= 2 * n; i++)
                if (cmp(a[i], b[i]) == 1 && arta[i] && cntb < n)
                {
                    cntb++;
                    a[i]++, b[i]++;
                    if (a[i] == 4)
                        a[i] = 1;
                    if (b[i] == 4)
                        b[i] = 1;
                    if (a[i] == ta[i])
                    {
                        a[i]++, b[i]++;
                        if (a[i] == 4)
                            a[i] = 1;
                        if (b[i] == 4)
                            b[i] = 1;
                    }
                    break;
                }
        assert(cnta == n && cntb == n);
        int sum = 0;
        for (int i = 1; i <= 2 * n; i++)
            sum += cmp(a[i], b[i]);
        cout << sum << endl;
        for (int i = 1; i <= 2 * n; i++)
            cout << a[i] << " ";
        cout << endl;
        for (int i = 1; i <= 2 * n; i++)
            cout << b[i] << " ";
        cout << endl;
    }
}

T4 Magical Expression

给定一个合法的含 |&?01 的后缀表达式(以字符串形式给出,其为“合法的”代表将所有 ? 替换为运算符后其会成为一个合法的后缀表达式),试求出其有多少个子串满足:

  • 将所有的 ? 替换成 |& 后,该串为合法的后缀表达式;
  • 存在一种将所有的 ? 替换成 |& 的方法使得该式所得结果为 0,同时也存在替换方法使得结果为 1。

请输出这样的子串的数量。两个子串是不同的仅当它们长度不同或位置不同。

比T3还要签到的一道题,但细节也比较多,最终也没能调出来.

总结

本场比赛码量较大,难度不大,但由于码力太差只切掉了三个题,luogu仅排到rk204,主要问题在于:

  • T1没有注意到数据范围是钦定暴力做法,想了较长时间没有必要的优化.
  • T2没有一遍AC.
  • T3一开始想的是把赢改为平.
  • T4这一类的表达式题写的不熟练.

CF549B Looksery Party(看了题解)

给定一张\\(n\\)个点的有向图,不一定联通,每个点都有一个自环.对于每个点\\(i\\)给出\\(a_i\\),试构造一种选点方案使得每个点的入度都不等于\\(a_i\\).

容易发现,对于\\(a_i=0\\)的点,我们必须将它们选进去.选入之后我们将该点所指向的点\\(a_i\\)减1,实际上相当于比较方便地统计当前选点产生的对其他点的入度.每次选出\\(a_i=0\\)的点,重复上述操作,正确性显然.同时也说明并不存在无解的情况.

#include <iostream>
#include <cstdio>
#include <assert.h>
using namespace std;
const int N = 105;
struct edge
{
    int to, next;
} e[N * N];
int cnt, head[N];
void add(int u, int v)
{
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}
int n;
int a[N], p[N], ans;
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        string s;
        cin >> s;
        for (int j = 0; j < s.length(); j++)
            if (s[j] == \'1\')
                add(i, j + 1);
    }
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    while (1)
    {
        bool flag = false;
        for (int i = 1; i <= n; i++)
            if (a[i] == 0 && !p[i])
            {
                ans++;
                p[i] = 1;
                flag = true;
                for (int j = head[i]; j; j = e[j].next)
                    a[e[j].to]--;
                break;
            }
        if (!flag)
            break;
    }
    cout << ans << endl;
    for (int i = 1; i <= n; i++)
        if (p[i])
            cout << i << " ";
    cout << endl;
}

CF525D Arthur and Walls(写的是错解)

给出\\(n \\times m\\)​的矩阵,有*,.两种符号,要求将最少的*变成.让所有.的连通块变为矩形.

法1:乱搞:

搜一遍记录每个连通块的端点,将其视为矩形,用扫描线合并相交的矩形.不保证能过.

法2:反向乱搞:

从每个*搜,看周围是否被连续的一个拐角形.包围,若是则修改.复杂度比较优秀.

莫名其妙C++17会爆栈.

#include <iostream>
#include <cstdio>
#include<assert.h>
using namespace std;
char map[2021][2021];
int n, m;
const int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1}, dy[8] = {1, -1, 0, 0, -1, 1, 1, -1};
void dfs(int x, int y)
{
	if (x-1<0||y-1<0)
	  return;
	assert(x<=2000&&y<=2000);
    if ((map[x + 1][y] == \'.\' && map[x][y + 1] == \'.\' && map[x + 1][y + 1] == \'.\') || (map[x + 1][y] == \'.\' && map[x][y - 1] == \'.\' && map[x + 1][y - 1] == \'.\') || (map[x - 1][y] == \'.\' && map[x][y + 1] == \'.\' && map[x - 1][y + 1] == \'.\') || (map[x - 1][y] == \'.\' && map[x][y - 1] == \'.\' && map[x - 1][y - 1] == \'.\'))
    {
        map[x][y] = \'.\';
        for (int i = 0; i <= 7; i++)
        {
            int tx = x + dx[i], ty = y + dy[i];
            if (tx > n || tx < 1 || ty > m || ty < 1)
                continue;
            if (map[tx][ty] == \'.\')
                continue;
            dfs(x + dx[i], y + dy[i]);
        }
    }
}
int main()
{
    //freopen("1.in", "r", stdin);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> map[i][j];
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (map[i][j] == \'*\')
                dfs(i, j);
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
            cout << map[i][j];
        cout << endl;
    }
    return 0;
}

CF1051F The Shortest Statement(自己做出)

给定一个树,在树上加k条边,求全源最短路。(\\(n \\le 10^5 \\space, k \\le 20\\))

显然利用树的性质,考虑如何处理加的20条边即可。可以枚举所有经过的非树边,对每个非树边的端点依次更新答案,再结合树上路径长度即可。

最小生成树+lca+dij即可解决的一道紫题...

写的时候要注意不同的图、树的混淆问题。

把lca板子写错调了一小时??? 有时候对树上深度总有种从下到上的错觉。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <assert.h>
#include <map>
#define ll long long
using namespace std;
const ll N = 100005, M = 200005,INF = 1e14;
ll n, m, Q;
ll cnt1, head1[M], cnt2, head2[M];
ll cnt, vis[N], dep[N], fa[N], f[N][23];
ll dis[45][N], treedis[N], tot,await[N];
struct edge {
	ll from, to, next;
	ll dis;
} e1[M], e2[M];
struct EDGE {
	ll u, v, d;
} e[M];
int read() {
	register int x=0,f=1;
	register char s=getchar();
	while(s>\'9\'||s<\'0\') {
		if(s==\'-\') f=-1;
		s=getchar();
	}
	while(s>=\'0\'&&s<=\'9\') {
		x=x*10+s-\'0\';
		s=getchar();
	}
	return x*f;
}
void add1(ll u, ll v, ll dis) {
	e1[++cnt1].from = u;
	e1[cnt1].to = v;
	e1[cnt1].next = head1[u];
	e1[cnt1].dis = dis;
	head1[u] = cnt1;
}
void add2(ll u, ll v, ll dis) {
	e2[++cnt2].to = v;
	e2[cnt2].from = u;
	e2[cnt2].next = head2[u];
	e2[cnt2].dis = dis;
	head2[u] = cnt2;
}
int cmp(EDGE a, EDGE b) {
	return a.d < b.d;
}
ll find(ll k) {
	return fa[k] == k ? k : fa[k] = find(fa[k]);
}
void dij(ll k) {
	for (int i=1;i<=n;i++)
	  vis[i]=0,dis[k][i]=INF;
	priority_queue<pair<ll,int> > q;
	dis[k][await[k]] = 0ll;
	q.push(make_pair(0,await[k]));
	while (!q.empty()) {
		ll x = q.top().second;
		q.pop();
		if (vis[x]) continue;
		vis[x] = 1;
		for (ll i = head1[x]; i; i = e1[i].next) {
			ll v = e1[i].to;
			if (dis[k][v] > dis[k][x] + e1[i].dis) {
				dis[k][v] = dis[k][x] + e1[i].dis;
				q.push(make_pair(-dis[k][v],v));
			}
		}
	}
}
void dfs(ll k) {
	for (int i=1; i<=20; i++) f[k][i]=f[f[k][i-1]][i-1];
	for (ll i = head2[k]; i; i = e2[i].next) {
		ll v = e2[i].to;
		if (v == f[k][0]) continue;
		f[v][0]=k;
		treedis[v]=treedis[k]+e2[i].dis;
		dep[v]=dep[k]+1;
		dfs(v);
	}
}
ll lca(ll a, ll b) {
	if (dep[a] < dep[b]) swap(a, b);
	for (ll i = 20; i >= 0; i--)
		if (dep[f[a][i]] >= dep[b])
			a = f[a][i];
	if (a == b) return a;
	for (ll i = 20; i >= 0; i--)
		if (f[a][i] != f[b][i]) a = f[a][i], b = f[b][i];
	return f[a][0];
}
int main() {
	n=read(),m=read();
	for (ll i = 1; i <= n; i++) fa[i] = i;
	for (ll i = 1; i <= m; i++) {
		e[i].u=read(),e[i].v=read(),e[i].d=read();
		add1(e[i].u, e[i].v, e[i].d),add1(e[i].v, e[i].u, e[i].d);
	}
	sort(e + 1, e + 1 + m, cmp);
	for (ll i = 1; i <= m; i++) {
		ll f1 = find(e[i].u), f2 = find(e[i].v);
		if (f1 != f2) {
			add2(e[i].u, e[i].v, e[i].d);
			add2(e[i].v, e[i].u, e[i].d);
			fa[f1] = f2;
		} else {
			await[++cnt]=e[i].u;
			await[++cnt]=e[i].v;
		}
	}
	for (int i=1; i<=cnt; i++)  dij(i);
	cin >> Q;
	dfs(1);
	while (Q--) {
		ll x = read(), y = read();
		ll ans = treedis[x] + treedis[y] - 2 * treedis[lca(x, y)];
		for (ll i = 1; i <= cnt; i++)
			ans=min(ans,dis[i][x] + dis[i][y]);
		cout << ans << endl;
	}
}

P3390 【模板】矩阵快速幂

只是模板而已。

但是没注意到输入都要开long long,还是WA+T了几次。以后还是在不MLE的情况下全开long long,省的乱七八糟的事。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=105;
const int Mod=1000000007;
int n;
long long t;
struct martix {
	long long fac[N][N];
	martix() {
		memset(fac,0,sizeof(fac));
	}
	void build() {
		for (int i=1; i<=n; i++)
			fac[i][i]=1;
	}
} Ans,Base;
martix operator *(const martix &A,const martix &B) {
	martix temp;
	for (int k=1; k<=n; k++)
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
				temp.fac[i][j]=(temp.fac[i][j]+A.fac[i][k]*B.fac[k][j]%Mod)%Mod;
	return temp;
}
int main() {
	ios::sync_with_stdio(false);
	cin>>n>>t;
	Ans.build();
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++)
			cin>>Base.fac[i][j];
	while (t>0) {
		if (t&1)
			Ans=Ans*Base;
		Base=Base*Base;
		t>>=1;
	}
	for (int i=1; i<=n; i++) {
		for (int j=1; j<=n; j++)
			cout<<Ans.fac[i][j]<<" ";
		cout<<endl;
	}
}

CF543B Destroying Roads(没有考虑到分类讨论)

边权全为1的无向连通图,最大化不在s1到t1,s2到t2两条路径上的边数.\\(n \\le 3000\\)

直接最短路明显是错的,因为将两路径重合部分计算了两次.由于n只有3000,可以先求出全源最短路(别nt用floyd),然后枚举重合部分的左右端点,注意s1,s2可能同侧或依次,分类讨论两种情况即可.

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int N = 3005, M = 3005 * 3005, INF = 0x3f3f3f3f;
int read()
{
    int f = 1, x = 0;
    char ch = getchar();
    while (ch < \'0\' || ch > \'9\')
    {
        if (ch == \'-\')
            f = -1;
        ch = getchar();
    }
    while (ch >= \'0\' && ch <= \'9\')
    {
        x = x * 10 + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
int n, m, s1, t1, l1, s2, t2, l2, ans = -1;
int dis[N][N];
int cnt, head[N];
struct edge
{
    int to, next;
} e[M];
void add(int u, int v)
{
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}
void bfs(int s)
{
    queue<int> q;
    while (!q.empty())
        q.pop();
    dis[s][s] = 0;
    q.push(s);
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        for (int i = head[x]; i; i = e[i].next)
        {
            int v = e[i].to;
            if (dis[s][v] != INF)
                continue;
            dis[s][v] = dis[s][x] + 1;
            q.push(v);
        }
    }
}
int main()
{
    n = read(), m = read();
    for (int i = 1; i <= m; i++)
    {
        int u = read(), v = read();
        add(u, v), add(v, u);
    }
    cin >> s1 >> t1 >> l1 >> s2 >> t2 >> l2;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            dis[i][j] = INF;
    for (int i = 1; i <= n; i++)
        bfs(i);
    if (dis[s1][t1] <= l1 && dis[s2][t2] <= l2)
        ans = max(ans, m - dis[s1][t1] - dis[s2][t2]);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
        {
            if (dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[s2][i] + dis[i][j] + dis[j][t2] <= l2)
                ans = max(ans, m - dis[s1][i] - dis[j][t1] - dis[s2][i] - dis[j][t2] - dis[i][j]);
            if (dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[t2][i] + dis[i][j] + dis[j][s2] <= l2)
                ans = max(ans, m - dis[s1][i] - dis[j][t1] - dis[t2][i] - dis[j][s2] - dis[i][j]);
        }
    cout << ans << endl;
}

P4551 最长异或路径(模板题)

给一棵边带权的树,求一条路径使得路径上所有边的异或最大.

容易发现abb=a,所以可以将两条重合的异或路径合并而不影响结果.考虑求出从根节点到每个节点的异或值,将这些值放进0/1trie树里,从高位贪心即可.

注意:根据讨论结果,还是将初始tot和now赋成1比较好,并且中间变量要取成bool型.

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100005;
int n, tot;
int val[N];
int cnt, head[N];
struct edge
{
    int from, to, next, d;
} e[N * 2];
int tree[N << 5][2];
void add(int u, int v, int d)
{
    e[++cnt].to = v;
    e[cnt].from = u;
    e[cnt].next = head[u];
    e[cnt].d = d;
    head[u] = cnt;
}
void build(int k, int bef)
{
    for (int i = head[k]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == bef)
            continue;
        val[v] = val[k] ^ e[i].d; //把根节点到每个节点的路径异或求出来
        build(v, k);
    }
}
void addtree(int v)
{
    int now = 0;
    for (int i = (1 << 30); i; i >>= 1)
    {
        bool temp = v & i; //将每一位取出来
        if (!tree[now][temp])
            tree[now][temp] = ++tot; //将now的左右结点加入并编号,显然应当编一次号
        now = tree[now][temp];       //迭代到对于插入的v的i位对应的结点
    }
}
int query(int v)
{
    int ans = 0, now = 0;
    for (int i = (1 << 30); i; i >>= 1)
    {
        bool temp = v & i;
        if (tree[now][temp ^ 1]) //如果trie树上存在与第i位0/1不同的结点,就选,否则不得不算等于0的点
        {                        //显然从高位到低位贪心即可.
            ans = (ans << 1) | (temp ^ 1);
            now = tree[now][temp ^ 1]; //迭代到下一节点
        }
        else
        {
            ans = (ans << 1) | temp;
            now = tree[now][temp]; //迭代到下一节点
        }
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n - 1; i++)
    {
        int u, v, d;
        cin >> u >> v >> d;
        add(u, v, d);
        add(v, u, d);
    }
    build(1, 0);
    for (int i = 1; i <= n; i++)
        addtree(val[i]);
    int maxn = 0;
    for (int i = 1; i <= n; i++)
        maxn = max(maxn, query(val[i]) ^ val[i]);
    cout << maxn << endl;
}

CF817E Choosing The Commander(想到正解但未写对)

有Q次操作,每次操作有三种类型,分别是

1 pi 把 pi 加入集合 S
2 pi 把 pi 从集合 S 中删除
3 pi li 表示查询集合中有多少个元素异或上 pi 后 小于 li

0/1trie树.显然对于每一位,若这一位异或后比li的这一位小,那么该结点所在的子树异或后都必然小于li,而若与li的这一位相等,则向这边迭代.

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 3000005;
int n, tot = 1;
int val[N], tree[N][2];
void add(int k, int v)
{
    int now = 1;
    for (int i = (1 << 30); i; i >>= 1)
    {
        val[now] += v;
        bool temp = k & i;
        if (!tree[now][temp])
            tree[now][temp] = ++tot;
        now = tree[now][temp];
    }
    val[now] += v;
}
int query(int v, int l)
{
    int now = 1, ans = 0;
    for (int i = (1 << 30); i; i >>= 1)
    {
        bool temp1 = v & i, temp2 = l & i;
        if (temp1 < temp2)
            ans += val[tree[now][0]];
        if (temp1 && temp2)
            ans += val[tree[now][1]];
        if (temp2)
            now = tree[now][temp1 ^ 1];
        else
            now = tree[now][temp1];
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        if (x == 1)
        {
            int v;
            cin >> v;
            add(v, 1);
        }
        if (x == 2)
        {
            int v;
            cin >> v;
            add(v, -1);
        }
        if (x == 3)
        {
            int v, l;
            cin >> v >> l;
            cout << query(v, l) << endl;
        }
    }
}

CF768E Game of Stones(看了题解)

有n堆石子,对于任意一堆,取的个数不能与之前任何一次取这堆取的个数相同.两人轮流取,问先手是否必胜.

显然对于任意一堆石子,取的次数最多的方案是从1开始依次递增取(最后若有剩余就合并到最后一次取的中去),比这种方案取的次数少的方案显然都是成立的,由于我们只关心某堆石子到底取多少次,显然转化为NIM游戏(因为NIM游戏就是每堆石子存在取1-n次取完的方案,容易发现,这与本问题是一致的,因为石子在影响胜负这个问题上的本质是其取的次数方案而不是他它的个数,即由个数和取法共同决定的.)将所有次数异或起来即为结果.

解题关键在于认识到NIM游戏的本质.

#include <iostream>
#include <cstdio>
using namespace std;
int n, ans, temp, sum;
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        temp = 0, sum = 0;
        cin >> temp;
        for (int i = 1;; i++)
        {
            sum += i;
            if (sum == temp)
            {
                temp = i;
                break;
            }
            if (sum > temp)
            {
                temp = i - 1;
                break;
            }
        }
        ans ^= temp;
    }
    if (ans)
        cout << "NO" << endl;
    else
        cout << "YES" << endl;
}

CF940E Cashback(想出结论,但没想到dp)

给出一个长度为n的序列,和整数c.将其划分为任意段.对于每段,去掉前\\(\\frac{len}{c}\\)(下取整)小的数,最小化剩下的数的和.

容易发现,对于小于c长度的段,不妨将其划分成长度为1,这样是等效的,且有利于最小化.对于长度大于2c的段,先把多出2c的部分拆成长度为1的段,然后把2c拆成两个c,这样一定不会变劣.原因在于较长段的最小和次小不可能比划分成两个较短段的最小值大.所以只有两种划分:长度为1,长度为c.(自己想到了这里)

考虑转移,容易发现如果选长度为1的段,会使答案直接多a_i,而选择长度为c的段,会使答案多sum-min,即区间和去掉一个最小值.用单调队列求出长度为c的所有段的最小值.转移即可.

还是要多注意单调队列写法.

#include <iostream>
#include <cstdio>
#include <queue>
#define int long long
using namespace std;
const int N = 100005, INF = 0x7fffffff;
int n, c, head = 1, tail = 0;
int a[N], q[N], g[N], sum[N], f[N];
signed main()
{
    ios::sync_with_stdio(false);
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    for (int i = 1; i <= n; ++i)
    {
        while (head <= tail && q[head] + c <= i)
            head++;
        while (head <= tail && a[q[tail]] >= a[i])
            tail--;
        q[++tail] = i;
        g[i] = a[q[head]];
    }
    for (int i = 1; i <= n; i++)
        if (i >= c)
            f[i] = min(f[i - 1] + a[i], f[i - c] + sum[i] - sum[i - c] - g[i]);
        else
            f[i] = f[i - 1] + a[i];
    cout << f[n] << endl;
}

CF1584

A Mathematical Addition

给出 \\(u,v\\) , 求方程\\(\\frac{x}{u}+\\frac{y}{v}=\\frac{x+y}{u+v}\\)的一组整数解.

暴力合并分数可得:

\\[xv^2+yu^2=0 \\]

显然\\(x=u^2,y=-v^2\\)即可.注意防止爆int.

#include <iostream>
#include <cstdio>
using namespace std;
int T;
long long a, b, x, y;
int main()
{
    ios::sync_with_stdio(false);
    cin >> T;
    while (T--)
    {
        cin >> a >> b;
        x = a * a, y = b * b;
        cout << x << " " << -y << endl;
    }
}

B Coloring Rectangles

给出一个\\(n \\times m\\)的矩形.初始全是红色,将其任意横竖划分成小矩形(不能是\\(1\\times 1\\)),再将一些色块染成蓝色,使没有一个矩形里相邻块颜色相同,任意划分,最小化必须染成蓝色的格子数.

由于任意划分,不妨尽量划分成\\(1\\times3\\) 的块,保证一个蓝块覆盖两个红块.

注意竖直划分完之后还可以水平划分.

写的还是比较简洁的.

#include <iostream>
#include <cstdio>
using namespace std;
long long T, n, m;
long long solve(long long r, long long c)
{
    if (r == 1)
    {
        if (c % 3 == 0)
            return c / 3;
        else
            return c / 3 + 1;
    }
    if (r == 2 && c == 2)
        return 2;
    if (r % 3 == 0)
        return c * (r / 3);
    else
        return c * (r / 3) + solve(c, r % 3);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> T;
    while (T--)
    {
        cin >> n >> m;
        cout << min(solve(m, n), solve(n, m)) << endl;
    }
}

C Two Arrays

给两个序列a,b,问能否通过一次如下操作把a变成b:

使a中任意个数+1,然后随意改变a的顺序.

想复杂了,以为是二分图,画了几个样例发现很符合,但估计过不去.

yyq云:排序

然后就没有然后了.

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 105;
int T, n;
int a[N], b[N];
int main()
{
    ios::sync_with_stdio(false);
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i <= n; i++)
            cin >> b[i];
        sort(a + 1, a + 1 + n);
        sort(b + 1, b + 1 + n);
        bool flag = true;
        for (int i = 1; i <= n; i++)
            if (a[i] != b[i] && a[i] + 1 != b[i])
            {
                cout << "NO" << endl;
                flag = false;
                break;
            }
        if (flag)
            cout << "YES" << endl;
    }
}

D Guess the Permutation

长度为n的序列\\(a\\)一开始满足\\(a_i=i\\),已经经过下面一次操作:

选择\\(i,j,k\\),将\\([i,j-1]\\)取反,\\([j,k]\\)取反.

每次可以向交互库询问区间逆序对数,最多询问40次,确定i,j,k.(\\(n\\leq 10^9\\))

考场上英语不好,读了半天读不下去了,去做E,发现不可做,就弃赛了.

总结

这次比赛ABC均为结论题(感觉后面也有点像,但我不会),其中B题较有新意.A,C应该属于送分题难度,但是C却想复杂了,最后开题顺序问题导致只做了三个题.排了rk2000/10000,不太理想.以后要对一些奇怪的结论敢于尝试,快速写出代码或者快速hack掉,不能在证明上浪费太多时间.另外要把握题目难度,防止误判.

CF1605

A A.M. Deviation

在三元组\\(a1,a2,a3\\)上定义d运算:\\(d=\\abs{a_1+a_3-2a_2}\\).

可以做任意多次如下操作:

选择\\(a1,a2,a3\\)中两个数,将其分别+1和-1.

对于给定的\\(a1,a2,a3\\) ,最小化d 的值.

容易发现结果只可能是0/1,只与和有关.

#include<iostream>
#include<cstdio>
using namespace std;
int main(){
    ios::sync_with_stdio(false);
    int T=0;
    cin>>T;
    while (T--){
        int a=0,b=0,c=0;
        cin>>a>>b>>c;
        if ((a+b+c)%3==0)
          cout<<0<<endl;
        else
          cout<<1<<endl;
    }
}

B Reverse Sort

给定一个01串,每次进行如下操作:

选择串中任意个数,它们形成不升序列,将它们对称翻转.

问将原序列变为不降序列的最少操作数.输出方案.

容易发现最多也只需要一次翻转,只需要找到左边的1和右边的0一样多的位置翻转即可.

代码写的很丑...

#include<iostream>
#include<cstdio>
usi

以上是关于2021年11月的做题记录的主要内容,如果未能解决你的问题,请参考以下文章

退役前的做题记录3

省选前的做题记录(Round2)

退役前的做题记录

csp退役前的做题计划1(真)

行测图形题目的做题总结及方法

2019-9-9做题记录