2023牛客暑期多校训练营2 补题

D The Game of Eating 结论,贪心

E Square 数论,枚举

F Link with Chess Game 博弈论,打表

G Link with Centrally Symmetric Strings 最长回文子串,结论Manacher算法
H 0 and 1 in BIT 线段树,结论推导

I Link with Gomoku 构造

Box 动态规划 

 

观察样例知,从头开始每次选择最大的时候,不会更优。反而倒着每次选择最大值,会得出解。而至于正确性,很难证明。 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[2020][2020];
int book[2020];
struct node
{
    int id,val;
    friend bool operator<(node x,node y)
    {
        return x.val<y.val;
    }
};
priority_queue<node>q[2020];
int main()
{

   int t;
   cin>>t;
   while(t--)
   {

       int n,m,k;
       cin>>n>>m>>k;

       for(int i=1;i<=m;i++)
       {
           book[i]=0;
       }
       for(int i=1;i<=n;i++)
       {
           while(!q[i].empty())
            q[i].pop();
           for(int j=1;j<=m;j++)
           {
               scanf("%d",&a[i][j]);
               struct node now;
               now.id=j;
               now.val=a[i][j];
               q[i].push(now);
           }
       }

       for(int i=k;i>=1;i--)
       {
           int now=i%n;
           if(now==0)
            now=n;

           while(!q[now].empty()&&book[q[now].top().id])
            q[now].pop();

           book[q[now].top().id]=1;
           q[now].pop();
       }

       for(int i=1;i<=m;i++)
       {
           if(book[i])
           {
               cout<<i<<" ";
           }
       }

       cout<<'\n';
   }
    return 0;
}

考虑将x开平方,再平方,如果恰好能够得出最好,如果不能得出,必须对要平方的数字加1,且只加1更接近这一前缀。但这一加1操作,会导致同等长度的前缀大于x,而考虑到+1操作带来的额外影响会随着x*10不断变小,故对x不停的*10,算出x为这些数字前缀时,y是否符合。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

bool check(ll y,ll x)
{
    ll now=y*y;
    ll ji=1;
    while(ji<=now)
    {
        if(now/ji==x)
            return 1;
       if(ji>=1e18)
           break;
        ji*=10ll;
    }
    return 0;
}
int main()
{

    int t;
    cin>>t;

    while(t--)
    {

        ll x;
        cin>>x;
        if(x==0)
        {
            cout<<0<<'\n';
            continue;
        }
        else
        {

            ll ans=-1;
            for(ll now=x;now<=1e18;now*=10ll)
            {
                ll temp=sqrt(now);
                if(temp*temp<now)
                    temp++;
                if(check(temp,x))
                {
                    ans=temp;
                    break;
                }
               
            }
            cout<<ans<<'\n';
        }
    }

    return 0;
}

 

打表找规律,首先对于一个,r,g,b状态,如果他的后续状态有输的,就代表我先手当前一定可以通过到达这一后续状态来实现赢。dfs暴力打表,可以得出,n为偶数时,一定是先手赢。否则,是先后手交替赢。 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;


int main()
{

   int t;
   cin>>t;
   while(t--)
   {
      ll n,a,b,c;
      cin>>n>>a>>b>>c;
      if(n%2==0)
      {
          cout<<"Alice"<<'\n';
          continue;
      }
      ll sum=(a-1)*n*n+(b-1)*n+c;
      if(sum%2)
      {
          cout<<"Bob"<<'\n';
      }
      else
      {
          cout<<"Alice"<<'\n';
      }
   }
    return 0;
}

 

 首先一个重要的结论是,回文串是能割就割。例如,

1 2 3 4 4 3 2 1

1 2 3 4 4 3 2 1 .... 1 2 3 4 4 3 2 1 

在同时面对割小串还是大串时,先割大串与割三个小串等价。故只需要找到能割的就割。

考虑manacher算法。起点开始时,如果发现一个回文串,也就是回文串中点到达了1号点,就割掉。然后令设置下一个起点即可。而为了防止新的起点之后的遍历跑到起点之前寻找回文串前部分,直接让新起点之前的字符变成无法匹配的即可。

最后只需要判断,最终的起点,是不是n即可。如果是n,代表全部切割,否则无法实现。

# include<bits/stdc++.h>
#define MN 1000000
using namespace std;

int rev[256];

char _s[MN+5],s[MN*2+5];

bool dc(char a,char b){
	return b==rev[a];
}
bool checkzhong(char ch)
{
    return (ch=='o'||ch=='s'||ch=='x'||ch=='z'||ch=='#');
}
void init(){
	for(int i=0;i<256;i++){
		rev[i] = 1;
	}
	rev['b'] = 'q';
	rev['d'] = 'p';
	rev['p'] = 'd';
	rev['q'] = 'b';
	rev['n'] = 'u';
	rev['u'] = 'n';
	rev['o'] = 'o';
	rev['s'] = 's';
	rev['x'] = 'x';
	rev['z'] = 'z';
	rev['#'] = '#';
}

void solve(){
	scanf("%s",_s);
	int n = strlen(_s);
	s[0] = '$';
	for(int i=0;i<n;i++){
		s[i*2+1] = '#';
		s[i*2+2] = _s[i];
	}
	n = n*2;
	s[++n] = '#';
	s[n+1] = '@';
	n++;
	int rc = 0;
	vector<int> r(n+1);
	int start = 1;
	for(int i=1;i<=n;i++){
		if(rc+r[rc]>=i){
			r[i] = min(r[rc-(i-rc)],r[rc]-(i-rc));
		}else{

            if(checkzhong(s[i]))
                r[i]=1;
            else
                r[i]=0;
		}
		while(dc(s[i-r[i]],s[i+r[i]])){
			r[i]++;
		}
		if(i+r[i]>rc+r[rc]){
			rc = i;
		}
		if(r[i]>0&&i-r[i]<start){
			s[i+r[i]-1] = '$';
			start = i+r[i];
			i = i+r[i]-1;
            rc=0;
		}
	}

	puts(start==n?"Yes":"No");
}

int main(){
	init();
	int T;
	scanf("%d",&T);
	while(T--) solve();
}

 

    以0100100为例
    反加 0100100->1011011-> 1011100   等于 0100011 1011100  先减后取反
    加反 0100100->0100101-> 1011010   等于 先加后取反
    左边取反,右边要减的变成加 保持不变
    反加      反加
    1011100   0100011 0100100     保持不变
    左边取反,左边本来要减,右边原来加的变成减
    反加      加反
    1011100   1011101 0100010     原数-2
    左边加1,取反后,右边加的要减  保持不变
    加反      加反
    1011010   1011011 0100100     保持不变
    左边加1,取反后,右边减的变成加, 原数加2
    加反      反加
    1011010  0100101  0100110     原数加2

所以,可以线段树维护,存储当前区间是否有翻转和所加值大小(可以负数)。

最终先加值,再看是否翻转。

而,因为减的值可能过大,大于原数,考虑取模。

即,0001进行一次翻转后加三次,

0001->1110->1111->0000->0001

也就是0001->0000->(-1)->(15)->1111->(14)->1110->翻转-0001

所以对(1<<len)取模即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll change[200000*4+10],add[200000*4+10];
string s;
void pushup(int root)
{
    change[root]=change[root<<1]^change[root<<1|1];
    if(change[root<<1])
        add[root]=add[root<<1]-add[root<<1|1];
    else
        add[root]=add[root<<1]+add[root<<1|1];

}
void build(int root,int l,int r)
{
    if(l==r)
    {
        change[root]=(s[l]=='A');
        add[root]=(s[l]=='B');
        return ;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    pushup(root);
}
pair<ll,ll>getans(int root,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
    {
        return make_pair(change[root],add[root]);
    }
    int mid=(l+r)>>1;
    pair<ll,ll>ans;
    if(L<=mid)
        ans=getans(root<<1,l,mid,L,R);
    if(R>mid)
    {
        pair<ll,ll>temp=getans(root<<1|1,mid+1,r,L,R);
        if(ans.first)
            ans.second=ans.second-temp.second;
        else
            ans.second=ans.second+temp.second;

        ans.first=ans.first^temp.first;

    }
    return ans;
}
int main()
{
    /*

    0100100
    反加 0100100->1011011-> 1011100   等于 0100011 1011100  先减后取反
    加反 0100100->0100101-> 1011010   等于 先加后取反
    左边取反,右边要减的变成加 保持不变
    反加      反加
    1011100   0100011 0100100     保持不变
    左边取反,左边本来要减,右边原来加的变成减
    反加      加反
    1011100   1011101 0100010     原数-2
    左边加1,取反后,右边加的要减  保持不变
    加反      加反
    1011010   1011011 0100100     保持不变
    左边加1,取反后,右边减的变成加, 原数加2
    加反      反加
    1011010  0100101  0100110     原数加2
    10 3
    BAABABABBA
    2 4
    110
    */

    int n,t;
    cin>>n>>t;
    cin>>s;
    s=" "+s;
    build(1,1,n);

    ll l,r,ans=0;
    while(t--)
    {
        scanf("%lld%lld",&l,&r);

        ll prel=l,prer=r;

        l=min((ans^prel)%n+1,(ans^prer)%n+1);
        r=max((ans^prel)%n+1,(ans^prer)%n+1);

        int flag=getans(1,1,n,l,r).first;
        ll add=getans(1,1,n,l,r).second;

        string now;
        cin>>now;
        ll val=0;

        for(int i=now.length()-1; i>=0; i--)
        {
            if(now[i]=='1')
                val+=(1ll<<(now.length()-1-i));
        }
        val+=add;
        ll mod=(1ll<<(now.length()));

        ll vall=0;
        val%=mod;
        val+=mod;
        val%=mod;

        for(int i=now.length()-1; i>=0; i--)
        {
            if((val&(1ll<<i)))
            {
                vall+=(1ll<<i);
            }
        }
        ll ans1=vall;
        if(flag)
        {

            ll temp=0;
            for(int i=0; i<now.length(); i++)
            {
                if((vall&(1ll<<i))==0)
                {
                    temp+=(1ll<<i);
                }
            }
            ans1=temp;
        }
        ans=0;
        for(int i=now.length()-1; i>=0; i--)
        {
            if((ans1&(1ll<<i)))
            {
                cout<<1;

                ans+=(1ll<<i);
            }
            else
                cout<<0;
        }
        cout<<'\n';
    }

    return 0;
}

 

每行四个四个构造就行

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7;

int main()
{

    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        scanf("%d%d", &n, &m);

        if (n % 2 == 1)
        {
            int now = 0;
            for (int i = 1; i < n; i++)
            {
                int nowcnt = 0;
                int nownow = now;
                for (int j = 1; j <= m; j++)
                {
                    nowcnt++;
                    if (nowcnt <= 4)
                    {
                        if (nownow == 0)
                        {
                            cout << "x";
                        }
                        else
                        {
                            cout << "o";
                        }
                    }
                    if (nowcnt == 4)
                    {
                        nowcnt = 0;
                        nownow ^= 1;
                    }
                }
                cout << '\n';
                now ^= 1;
            }

            for (int i = 1; i <= m; i++)
            {
                if (i % 2 == 1)
                {
                    cout << 'x';
                }
                else
                {
                    cout << "o";
                }
            }
            cout << '\n';
        }
        else
        {
            int now = 0;
            for (int i = 1; i <= n; i++)
            {
                int nowcnt = 0;
                int nownow = now;
                for (int j = 1; j <= m; j++)
                {
                    nowcnt++;
                    if (nowcnt <= 4)
                    {
                        if (nownow == 0)
                        {
                            cout << "x";
                        }
                        else
                        {
                            cout << "o";
                        }
                    }
                    if (nowcnt == 4)
                    {
                        nowcnt = 0;
                        nownow ^= 1;
                    }
                }
                now ^= 1;
                cout << '\n';
            }
        }
    }

    return 0;
}

 dp[i][0/1/2]代表前i个在第i个是保持不变还是左移右移时的dp值

当前不移动时,因为不能重叠,所以考虑dp[i-1][0],dp[i-1][1]转移

当前向左时,dp[i-1][1]转移

当前向右时,dp[i-1][0/1/2]转移

以上是位置上有盖子的情况,没有盖子时,考虑继承

因为有盖子向左转移时只能由向左转移而来,故没有盖子的向左转移只能继承之前的“不动”“向左”

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7;
bool e[N];
ll a[N],dp[N][4];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        int inn;
        scanf("%d", &inn);
        e[i] = inn;
    }
    ll res = 0;
    for (int i = 1; i <= n; i++)
    {
        if (e[i])
        {
            dp[i][0] = dp[i - 1][0] + a[i - 1];
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0]) + a[i];
            dp[i][2] = max(dp[i - 1][0], max(dp[i - 1][2], dp[i - 1][1])) + a[i + 1];
        }
        else
        {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][2] = dp[i][1] = max(dp[i - 1][2], max(dp[i - 1][0], dp[i - 1][1]));
        }
        res = max(res, max(dp[i][0], max(dp[i][1], dp[i][2])));
    }
    printf("%lld\n", res);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦三码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值