2023“钉耙编程”中国大学生算法设计超级联赛(7)

B. Random Nim Game 结论,签到

D. Medians Strike Back 构造,打表

H. HEX-A-GONE Trails 博弈论,ST表

K. Three Operations 贪心,签到

M. Minimal and Maximal XOR Sum 思维,构造,逆序对

 

 打表发现a1>1的时候,概率总是1/2。随后,一旦有ai>1,概率也是1/2。除了全为1的情况特判,其余都是1/2.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        ll p0 = 1, p1 = 0;
        int n;
        scanf("%d", &n);
        bool ext = 0;
        for (int i = 0; i < n; i++)
        {
            int x;
            scanf("%d", &x);
            if (x > 1)
            {
                ext = 1;
            }
        }
        if (ext)
        {
            puts("499122177");
        }
        else if (n % 2)
        {
            puts("1");
        }
        else
        {
            puts("0");
        }
    }
}

 

 

虽然是可以构造的,但是构造起来难度不小,打表暴力搜索外加剪枝, 会发现,每段答案的起始点,奇数时是n*(n+1)-1,否则是n*n。所以直接二分即可。

/*#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100005], n;
int tr[100005];
int lowbit(int now)
{
    return now & (-now);
}
void add(int now)
{
    for (int i = now; i <= n; i += lowbit(i))
    {
        tr[i]++;
        tr[i] %= 2;
    }
}
int qry(int now)
{
    int res = 0;
    for (int i = now; i > 0; i -= lowbit(i))
    {
        res += tr[i];
        res %= 2;
    }
    return res;
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        if (n == 1)
        {
            cout << 0 << " " << 1 << "\n";
            continue;
        }
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
            ans += qry(n) - qry(a[i]);
            ans = (ans + 2) % 2;
            add(a[i]);
        }
        int mx = 0, idx;
        for (int i = 29; i >= 0; i--)
        {
            if ((n >> i) & 1)
            {
                idx = i;
                break;
            }
        }
        for (int i = idx; i >= 0; i--)
        {
            mx |= (1 << i);
        }
        // cout << "mx:" << mx << "\n";
        if (ans & 1)
        {
            cout << 2 << " " << mx << "\n";
        }
        else
        {
            mx -= 2;
            cout << 0 << " " << mx << "\n";
        }
        for (int i = 0; i <= n; i++)
        {
            tr[i] = 0;
        }
    }
    return 0;
}*/

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        ll n;
        scanf("%lld", &n);
        if (n <= 3)
        {
            cout << 1 << '\n';
        }
        else if (n <= 10)
        {
            cout << 2 << '\n';
        }
        else
        {
            ll l = 1, r = 1e9;
            while (l < r)
            {
                ll mid = (l + r + 1) >> 1;
                ll sum = 0;
                if (mid & 1)
                {
                    sum = mid * (mid + 1) - 1;
                }
                else
                {
                    sum = mid * mid;
                }
                if (sum > n)
                    r = mid - 1;
                else
                    l = mid;
            }
            cout << l << "\n";
        }
    }
    return 0;
}

 

 

 

 有这样一种策略,先手进入非ST->ED路径上的子树,有了ST的阻隔,先手会选择这些路径中最长的,同样因为ED不能进入ST的路径,所以只能选择一条最长的路径,这条路径可以是ED内部,也可以是先走到ST-ED路径某点,再进入其内部。如果二者路径前者大于后者,显然是一个必胜策略。否则,先手必败,则不会选择这样走,而是会直接走到图中虚线路径上,于是局面又回到刚开始的状态,只不过先手变成ED。

有了这一策略,可以预处理出图中虚线路径,每个路径点向非路径点延伸的最长路径长度。由于有了虚线路径的阻隔,这些每个点的最长路径都是独立的,互不影响。

以当前先手为例,我们需要查询len[st]的大小,以及ed到st右侧虚点路径上,每个点的最长路径+该点距离ed的长度(pos-ed) ,所以可以将len[pos]+pos加入St表或者其他区间最值数据结构进行区间查询。

 

#include <bits/stdc++.h>
using namespace std;
int maxx1[100000+10][31],maxx2[100000+10][31];
vector<int>v[100000+10];
int pre[100000+10],temp[100000+10],len,book[100000+10];
void dfs(int now,int fa)
{
    pre[now]=fa;
    for(auto it:v[now])
    {
        if(it==fa)
            continue;
        dfs(it,now);
    }
}
int maxlen[100000+10];
int getlen(int now,int fa)
{
    int maxx=0;
    for(auto it:v[now])
    {
        if(it==fa||book[it])
            continue;
        maxx=max(maxx,getlen(it,now)+1);
    }
    return maxx;
}
int lo[100000+10];
int getmax(int l,int r,int flag)
{
    if(flag==1)
    {
        int lo2=lo[r-l+1];
        return max(maxx1[l][lo2],maxx1[r-(1<<lo2)+1][lo2]);
    }
    else
    {
        int lo2=lo[r-l+1];
        return max(maxx2[l][lo2],maxx2[r-(1<<lo2)+1][lo2]);
    }
}
int main()
{
    cin.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        len=0;
        int st,ed;
        cin>>st>>ed;
        lo[0]=-1;
        for(int i=1;i<=n;i++)
        {
            v[i].clear();
            book[i]=0;
            pre[i]=0;
            maxlen[i]=0;
            lo[i]=lo[i/2]+1;
        }
        for(int i=1;i<n;i++)
        {
            int x,y;
            cin>>x>>y;
            v[x].push_back(y);
            v[y].push_back(x);
        }
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=30;j++)
            {
                maxx1[i][j]=maxx2[i][j]=0;
            }
        }
        dfs(st,0);
        int now=ed;
        while(now)
        {
            len++;
            temp[len]=now;
            book[now]=1;
            now=pre[now];
        }
        for(int i=1;i<=len;i++)
        {
            maxlen[i]=getlen(temp[i],0);
            maxx1[i][0]=maxlen[i]+i;
            maxx2[i][0]=maxlen[i]-i;
        }

        for(int j=1;j<=19;j++)
        {
            for(int i=1;i+(1<<j)-1<=n;i++)
            {
                maxx1[i][j]=max(maxx1[i][j-1],maxx1[i+(1<<(j-1))][j-1]);
                maxx2[i][j]=max(maxx2[i][j-1],maxx2[i+(1<<(j-1))][j-1]);
            }
        }

        int op1=len,op2=1,flag=0;
        while(1)
        {
            if(flag==0) //当前是
            {
                if(op1-1==op2)
                {
                    if(maxlen[op1]>maxlen[op2])
                    {
                        cout<<1<<'\n';
                        break;
                    }
                    else
                    {
                        cout<<0<<'\n';
                        break;
                    }
                }
                if(maxlen[op1]>getmax(op2,op1-1,1)-op2)
                {
                    cout<<1<<'\n';
                    break;
                }
                op1--;

            }
            else
            {
                 if(op2+1==op1)
                {
                    if(maxlen[op2]>maxlen[op1])
                    {
                        cout<<0<<'\n';
                        break;
                    }
                    else
                    {
                        cout<<1<<'\n';
                        break;
                    }
                }
                if(maxlen[op2]>getmax(op2+1,op1,2)+op1)
                {
                    cout<<0<<'\n';
                    break;
                }
                op2++;

            }
            flag^=1;
        }


    }
    return 0;
}

 

直接贪心取即可,注意不能用二三操作时,要直接退出,否则超时。 

首先,每次交换一个相邻的,交换总次数为逆序对数,则逆序对数为0时,最小的答案为0,否则是2。 否则,考虑构造一种填满n的二进制位数的方案。即每次交换长度为2的若干次幂的操作。每次交换2的次幂长度时,长度为2,奇偶性改变,4的时候,逆序对数由x转化为4*(4-1)/2=6-x,因为6是偶数,故奇偶性不变,又因为4之后长度皆为2的次幂,每次n*(n-1)/2都是偶数,故只有2的时候才会改变。所以得出结论,当逆序对位偶数时,最大为后一位扣除2,否则不扣除。特别的,因为长度为1的翻转一定可以,故最后1位一定取。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100005], n;
int tr[100005];
int lowbit(int now)
{
    return now & (-now);
}
void add(int now)
{
    for (int i = now; i <= n; i += lowbit(i))
    {
        tr[i]++;
        tr[i] %= 2;
    }
}
int qry(int now)
{
    int res = 0;
    for (int i = now; i > 0; i -= lowbit(i))
    {
        res += tr[i];
        res %= 2;
    }
    return res;
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        if (n == 1)
        {
            cout << 0 << " " << 1 << "\n";
            continue;
        }
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
            ans += qry(n) - qry(a[i]);
            ans = (ans + 2) % 2;
            add(a[i]);
        }
        int mx = 0, idx;
        for (int i = 29; i >= 0; i--)
        {
            if ((n >> i) & 1)
            {
                idx = i;
                break;
            }
        }
        for (int i = idx; i >= 0; i--)
        {
            mx |= (1 << i);
        }
        if (ans & 1)
        {
            cout << 2 << " " << mx << "\n";
        }
        else
        {
            mx -= 2;
            cout << 0 << " " << mx << "\n";
        }
        for (int i = 0; i <= n; i++)
        {
            tr[i] = 0;
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秦三码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值