相比上一场的自闭场这场着实舒服啊…

A - Common Subsequence

题意:求a和b的最短公共子序列
思路:找出相同的一个数字就行。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstdlib>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<set>
#define fi first
#define se second
#define pb push_back
#define me(a,b) memset(a,b,sizeof(a))
#define INIT() std::ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
const int MAX_N=200000+5;
const int MAX_M=100000+5;
const int INF=0x7fffffff;
const int inf=1000000000;
const double EPS=1e-6;
const ull base=123;
const ll mod=998244353;
const double pi=4*atan(1.0);
int a[MAX_N];
int b[MAX_N];
int main()
{
   
    int t;
    cin>>t;
    while(t--)
    {
   
        int n,m;
        int i,j;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        {
   
            scanf("%d",&a[i]);
        }
        for(i=1;i<=m;i++)
        {
   
            scanf("%d",&b[i]);
        }
        int ans=0;
        for(i=1;i<=n;i++)
        {
   
            for(j=1;j<=m;j++)
            {
   
                if(a[i]==b[j])
                {
   
                    ans=a[i];
                    break;
                }
                if(ans)
                    break;
            }
        }
        if(ans)
        {
   
            cout<<"YES"<<endl;
            cout<<1<<' '<<ans<<endl;
        }
        else
            cout<<"NO"<<endl;
    }
}

B. Sequential Nim

题意:有几堆石头,现在有两个人比赛,每个人可以轮流在第一个非空的堆里拿走任意个石头,最后没石头拿的人输,已知两人都会采取最佳策略,要求输出谁会赢。
思路:一堆石头可以分为两种情况,即只有1块石头,和有1块以上,如果只有1块石头,那么轮到的人是没有选择权的,必须全部拿完,但是如果有1块以上,那么这个人可以选择拿完这堆,让对方拿下一堆,或者拿到只剩1个,让对方拿最后一个,自己拿下一堆,所以有绝对的决定权,因此可以得出结论:谁先轮到不是1的堆谁就赢,全为1的情况考虑奇偶。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<iomanip>
    #include<algorithm>
    #include<stack>
    #include<queue>
    #include<cstdlib>
    #include<string>
    #include<vector>
    #include<list>
    #include<map>
    #include<set>
    #define fi first
    #define se second
    #define pb push_back
    #define me(a,b) memset(a,b,sizeof(a))
    #define INIT() std::ios::sync_with_stdio(false)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int>P;
    const int MAX_N=200000+5;
    const int MAX_M=100000+5;
    const int INF=0x7fffffff;
    const int inf=1000000000;
    const double EPS=1e-6;
    const ull base=123;
    const ll mod=998244353;
    const double pi=4*atan(1.0);
    int a[MAX_N];
    int main()
    {
   
        int t;
        cin>>t;
        while(t--)
        {
   
            int n;
            scanf("%d",&n);
            int i;
            for(i=1;i<=n;i++)
                scanf("%d",&a[i]);
            int flag=0;
            for(i=1;i<=n;i++)
            {
   
                if(a[i]!=1)
                {
   
                    if(i%2)
                    {
   
                        flag=1;
                    }
                    else
                        flag=2;
                    break;
                }
            }
            if(!flag)
            {
   
                if(n%2)
                {
   
                    cout<<"First"<<endl;
                }
                else
                    cout<<"Second"<<endl;
            }
            else
            {
   
                if(flag==1)
                    cout<<"First"<<endl;
                else
                    cout<<"Second"<<endl;
            }
        }
    }

C1 - Prefix Flip (Easy Version)

题意:给定a,b两个01序列,要求通过数次操作使a等于b。操作为使a的指定长度前缀,01互换,并且倒置。
思路:c1和c2题是一样的,就是数据范围c2卡的更严格,所以按理说c2的ac代码可以直接用来交c1,但本菜鸡不敢跳过c1做c2,怕血本无归,但做完发现其实c1到c2的做法思路是连贯的。由于长的前缀操作会影响到短的,所以我们考虑从后往前看。从后往前遍历,如果a[i]不等于b[i],那么为了让他们相同必然要进行i长度的操作,但是如果a[1]==b[i],翻转过来以后依然是不一样的,所以我们可以先进行一次1长度的操作,再进行i长度操作,遍历下来操作数不超过2*n完全符合题目要求的不超过3n,因为本题n不超过1000,可以O(n^2)暴力翻转。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstdlib>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<set>
#define fi first
#define se second
#define pb push_back
#define me(a,b) memset(a,b,sizeof(a))
#define INIT() std::ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
const int MAX_N=200000+5;
const int MAX_M=100000+5;
const int INF=0x7fffffff;
const int inf=1000000000;
const double EPS=1e-6;
const ull base=123;
const ll mod=998244353;
const double pi=4*atan(1.0);
char a[MAX_N];
char b[MAX_N];
char temp[MAX_N];
vector<int>ans;
int main()
{
   
    int t;
    cin>>t;
    while(t--)
    {
   
        ans.clear();
        int n;
        scanf("%d",&n);
        scanf("%s",a);
        scanf("%s",b);
        int i,j;
        for(i=n-1;i>=0;i--)
        {
   
            if(a[i]!=b[i])
            {
   
                if(a[0]!=b[i])
                {
   
                    ans.pb(i+1);
                    for(j=0;j<=i;j++)
                    {
   
                        if(a[j]=='0')
                            temp[j]='1';
                        else
                            temp[j]='0';
                    }
                    int pos=0;
                    for(j=i;j>=0;j--)
                    {
   
                        a[j]=temp[pos++];
                    }
                }
                else
                {
   
                    ans.pb(1);
                    if(a[0]=='0')
                        a[0]='1';
                    else
                        a[0]='0';
                    i++;
                    continue;
                }
            }
        }
        cout<<ans.size();
        for(auto i:ans)
        {
   
            cout<<' '<<i;
        }
        cout<<endl;
    }
}

C2 - Prefix Flip (Hard Version)

题意:同上。
思路:较c1的差别是,操作数改为不能超过2n以及n的范围扩大到1e5,操作数我们上一题本来就是2*n,所以方法不用换,现在要考虑如何降低复杂度,容易看出主要消耗复杂度的地方在于倒置操作的处理,所以我们可以先提前放置一个数组,是整个a的n长度操作后的数组。我们这里把一个序列标记为 1 2 3 4 5,那么倒置就是5 4 3 2 1,此时我们倒置前4个,就是2 3 4 5 1,倒置前三个是4 3 2 5 1,所以可以发现每次倒置就是从一个数组跳到另一个数组,并且起点变为上一次操作的末尾数。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstdlib>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<set>
#define fi first
#define se second
#define pb push_back
#define me(a,b) memset(a,b,sizeof(a))
#define INIT() std::ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
const int MAX_N=200000+5;
const int MAX_M=100000+5;
const int INF=0x7fffffff;
const int inf=1000000000;
const double EPS=1e-6;
const ull base=123;
const ll mod=998244353;
const double pi=4*atan(1.0);
char a1[MAX_N];
char a2[MAX_N];
char b[MAX_N];
char temp[MAX_N];
vector<int>ans;
int main()
{
   
    int t;
    cin>>t;
    while(t--)
    {
   
        ans.clear();
        int n;
        scanf("%d",&n);
        scanf("%s",a1);
        scanf("%s",b);
        int pos=0;
        int i,j;
        for(i=n-1;i>=0;i--)
        {
   
            if(a1[pos++]=='0')
                a2[i]='1';
            else
                a2[i]='0';
        }
        int pos1=0;
        int pos2=0;
        int flag=1;
        for(i=n-1;i>=0;i--)
        {
   
            if(flag==1)
            {
   
                if(a1[pos1+i]!=b[i])
                {
   
                    //pos1=pos2;
                    if(a1[pos1]==b[i])
                    {
   
                        ans.pb(1);
                    }
                    ans.pb(i+1);
                    flag=2;
                    //pos2=pos1;
                    pos2=n-(pos1+i)-1;
                }
 
            }
            else
            {
   
                if(a2[pos2+i]!=b[i])
                {
   
                    //pos2=pos1;
                    if(a2[pos2]==b[i])
                    {
   
                        ans.pb(1);
                    }
                    ans.pb(i+1);
                    flag=1;
                    //pos1=pos2;
                    pos1=n-(pos2+i)-1;
                }
 
            }
        }
        cout<<ans.size();
        for(auto i:ans)
        {
   
            cout<<' '<<i;
        }
        cout<<endl;
    }
}

D - Unmerge

题意:有两个序列,每次将这两个序列开头小的那个元素拿出,放入新序列末尾,最终形成新序列,现在给定该序列,问是否存在原来的两个序列,可以形成这个序列。
思路:通过观察可以看出,当这个序列某一段是递减的,那么这一段必然来自同一序列,那么就可以把这一段看成以连通块,题意就转变为,将多个连通块平均分到两队,用01背包实现。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstdlib>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<set>
#define fi first
#define se second
#define pb push_back
#define me(a,b) memset(a,b,sizeof(a))
#define INIT() std::ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
const int MAX_N=200000+5;
const int MAX_M=100000+5;
const int INF=0x7fffffff;
const int inf=1000000000;
const double EPS=1e-6;
const ull base=123;
const ll mod=998244353;
const double pi=4*atan(1.0);
int a[MAX_N];
vector<int>v;
int dp[MAX_N];
int main()
{
   
    int t;
    cin>>t;
    while(t--)
    {
   
        v.clear();
        int n;
        scanf("%d",&n);
        int i,j;
        n*=2;
        for(i=1;i<=n;i++)
        {
   
            scanf("%d",&a[i]);
        }
        for(i=1;i<=n;i++)
        {
   
            j=i;
            while(a[j]<=a[i]&&j<=n)
                j++;
            v.pb(j-i);
            i=j-1;
        }
        me(dp,0);
        dp[0]=1;
        for(auto i:v)
        {
   
            for(j=n;j>=i;j--)
            {
   
                if(dp[j-i])
                {
   
                    dp[j]=1;
                }
            }
        }
        if(dp[n/2])
            cout<<"YES"<<endl;
        else
            cout<<"NO"<<endl;
    }
}