UPC 个人训练赛1部分题题解

A 斗地主

由于数据范围不是多大,所以可以直接用搜索解决,因为这里是一个类似在二叉搜索树上求最短路的问题,故这里推荐bfs。

既然要用bfs,就需要像遍历图一样明确方向。求解这个问题需要三个人的得分情况,也就是需要讨论三个维度的方向问题,而且在遍历中需要标记数组来避免死循环,不巧的是这个数据范围(-300~300)不能让你开三维数组。但是题给了另外一个条件:a+b+c=0,所以只要求出两个人的得分,第三个人的得分就是唯一确定的了,故把方向数组和标记数组都压缩一维至二维。

对于一局斗地主,如果仅看两个人的得分情况的话,需要讨论倍数,输赢,阵营。即开局积分的倍数,是否赢下这一局,这两个人都是农民还是一个地主一个农民。对于同一种倍数的游戏,阵营分为三种情况:两农民,a农民b地主,b农民a地主,然后各自分输赢,也就是6种。而这里有3种倍数,则共有18种情况。

由于情况数较多,所以这里推荐在入队列前对状态进行标记以进行剪枝,以防过度消耗时间,同时建议对0的这个状态进行特判,这也对时间优化有利。

ll dir[30][2] = {
    {0,0},{-1,2},{2,-1},{1,-2},{-2,1},{1,1},{-1,-1}, {-2,4},{4,-2},{2,-4},{-4,2},{2,2},{-2,-2}
,{-3,6},{6,-3},{3,-6},{-6,3},{3,3},{-3,-3}
};
struct node
{
    ll cura, curb;
    ll round;
};
bool vis[1200][1200];
ll ans = 1002;
void bfs(ll ta,ll tb,ll tc,ll limit)
{
    queueq;
    ta += 300, tb += 300,tc+=300;
    q.push(node{ 300,300, 0});
    vis[300][300] = true;
    while (!q.empty())
    {
        node cur = q.front();
        q.pop();
        if (cur.round > limit)
            return ;
        if (cur.cura == ta && cur.curb == tb && cur.round >= 1)
        {
            ans = min(ans,cur.round);
        }
        else
        {
            for (int i = 1; i <= 18; i++)
            {
                ll nx = cur.cura + dir[i][0];
                ll ny = cur.curb + dir[i][1];
                //cout << nx << " " << ny << endl;
                if (nx <= 600 && nx >= 0 && ny >= 0 && ny <= 600)
                {
                    if (!vis[nx][ny])
                    {
                        vis[nx][ny] = true;
                        q.push(node{ nx, ny, cur.round + 1 });
                    }
                }
            }
        }
    }
}
int DETERMINATION()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0), std::cout.tie(0);
    //for (int i = 1; i <= 18; i++)
    //{
    //  cout << dir[i][0] << " " << dir[i][1] << endl;
    //}
    ll n, a, b, c;
    cin >> n >> a >> b >> c;
    ans = 1002;
    if (a == 0 && b == 0 && c == 0)
    {
        if (n >= 2)
        {
            cout << 2 << endl;
        }
        else
            cout << -1 << endl;
    }
    else
    {
        bfs(a, b, c, n);
        if (ans == 1002)
        {
            cout << -1 << endl;
        }
        else
            cout << ans << endl;
    }
    return 0;
}

B 种树

这是一个比较麻烦的普通dp,麻烦在各种状态的讨论上。

对于连续的三棵树,题意允许的序列是:

  • 1 2 1

  • 1 3 1

  • 2 1 3

  • 2 3 1

  • 3 1 3

  • 2 1 2

所以需要对六种状态分别讨论。可以看出每种下一棵树都需要考虑前面那棵树的品种,所以至少需要三维的数组来描述每种情况。但是这是一个环形区域,当你种到最后一棵树,即第n棵时,n-1 n 1这三棵树也形成了一个连续序列,所以第一棵树的品种也会影响到后续的种植,故描述状态的数组还需要再加一维变成四维数组,即当前种下的是第i棵树,当前种树的品种,前一棵树的品种,第一棵树的品种四维。

然后就是在状态转移中对上述六种序列分别进行状态转移即可,由于第一棵树并没有什么特别的约束条件,并且并不影响中间的过程(除了最后一棵树),所以可以直接for循环遍历讨论。

最后取答案的时候也是需要从上述六种序列的角度来求取最大值。

ll arr[100099][4];
ll pdd[100099][4][4][4];
int DETERMINATION()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0), std::cout.tie(0);
    ll n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> arr[i][1] >> arr[i][2] >> arr[i][3];
    pdd[1][1][2][1] = pdd[1][1][3][1] = arr[1][1];(这里是假设最后一棵树是某某品种)
    pdd[1][2][3][2] = pdd[1][2][1][2] = arr[1][2];
    pdd[1][3][1][3] = pdd[1][3][2][3] = arr[1][3];
    for (int i = 2; i <= n; i++)
    {
        for (int j = 1; j <= 3; j++)
        {
            pdd[i][1][2][j] = pdd[i - 1][2][1][j] + arr[i][1];
            pdd[i][1][3][j] = max(pdd[i - 1][3][1][j], pdd[i - 1][3][2][j]) + arr[i][1];
            pdd[i][2][1][j] = max(pdd[i - 1][1][3][j], pdd[i - 1][1][2][j]) + arr[i][2];
            pdd[i][2][3][j] = max(pdd[i - 1][3][2][j], pdd[i - 1][3][1][j]) + arr[i][2];
            pdd[i][3][1][j] = max(pdd[i - 1][1][3][j], pdd[i - 1][1][2][j]) + arr[i][3];
            pdd[i][3][2][j] = pdd[i - 1][2][3][j] + arr[i][3];
        }
    }
        ll ans1 = max(pdd[n][2][1][1],pdd[n][2][3][3]);
        ll ans2 = max({ pdd[n][1][3][3],pdd[n][1][2][2],pdd[n][1][2][3],pdd[n][1][3][2] });
        ll ans3 = max({ pdd[n][3][1][1],pdd[n][3][2][2],pdd[n][3][2][1],pdd[n][3][1][2] });
        cout << max({ ans1,ans2,ans3 }) << endl;
        return 0;
}

D 卡片

本题是一个关于变换规律的问题。

根据变换规律,可以发现:前半部分的牌之间都隔了一张从后半部分抽来的牌。

1 2 3 4 5 6->4 1 5 2 6 3,第一张牌前面加了一张牌,第二张牌前面加了两张牌(2的前面加了4,5),第三张牌前面加了三张牌(3的前面加了4,5,6),可以推测前半部分的第n张牌前面加了n张牌,从而可知第n个数的位置变成了2*n。

那么后半部分的有什么变化规律呢?实际上也是2*n,只不过是在周期内循环而已。比如上例的4,原位置是4,扩大倍之后变成8,然后在周期内循环至2号位,再减1就是当前的位置,即2*n-(N+1)

所以可以假定某个数通过x次前半部分式的变化和y次后半部分式的变化达到最终位置K即:2^{m}x+(n+1)y=FinalLoc,x是原位置,y是指代在变化中有y次位于后半部分。

根据拓展欧几里得定理可以解出上式,再将原式变化一下得到:

\frac{2^{m}x}{FinalLoc}+\frac{(n+1)y}{FinalLoc}=1,即起始位置位于x/Finalloc的元素会在m次变换后到达1号位置。再将这个式子乘以相应倍数就可以得到最终位置为K的元素的起始位置为x/FinalLoc*K.

ll quickpow(ll a, ll b, ll c)
{
	ll ans = 1;
	a %= c;
	while (b>0)
	{
		if ((b & 1) > 0)
			ans = (ans*a) % c;
		b >>= 1;
		a = (a*a) % c;
	}
	return ans;
}
ll exgcd(ll a, ll &x, ll b, ll &y)
{
	if (b == 0)
	{
		x = 1, y = 0;
		return a;
	}
	else
	{
		ll tmpgcd = exgcd(b, y, a%b, x);
		y -= (a / b)*x;
		return tmpgcd;
	}
}
int DETERMINATION()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0), std::cout.tie(0);
	ll n, m, p;
	cin >> n >> m >> p;
	ll fpara = quickpow(2, m, n + 1);
	ll spara = n + 1;
	ll x, y;
	ll ans = exgcd(fpara, x, spara, y);
	spara /= ans; p /= ans;
	x = (x*p) %((n+1)/ans);
	if (x <= 0)
		x += (n + 1) / ans;
	cout << x << endl;
	return 0;
}

 

你可能感兴趣的:(训练集)