Atcoder ABC 189 题解

D - Logical Expression

题目大意:给定(n)个字符串,每个都是AND或者OR.要求你构造(n+1)个值,记入({x0,x1,x2,...xn}).每个值只能取(0/1),同时根据你构造的(x)集合构造另外一个(y)集合:

  • (y0 = x0)
  • 对于(igeq 1),(y_i = y_{i-1} & x_i)当且仅当(s_i=)AND.(y_i=y_{i-1}| x_i)当且仅当(s_i=)OR.

如果(y_n=1)那么则称构造出此(y)集合的(x)集合是牛逼的,求牛逼的(x)集合的方案个数.

数据范围:

(1 leq n leq 60)

思路

直接dp没什么好说的.

  • 状态(f[i][j])表示当前的(y_i=j)的方案数
  • 入口:(f[0][0] = f[0][1] = 1)其余全(0).
  • 转移:按情况讨论即可,如果当前是取AND,那么当前的(y_i)如果想要是(1)必须要是(x_i=1)且上一位也是(1),(f[i][1]=f[i-1][1]),如果当前(y_i=0)那么当前(x_i=0)时上一位任意选择,如果当前(x_i=1)时上一位只能选择(0)也就是(f[i][0] = f[i - 1][1] + 2 * f[i - 1][0]).对于取OR的情况对称处理就可以了.
  • 出口:(f[n][1]).

直接暴力转移,反正就(60)位,方案数比较大,开(ll)就可以过了.

E - Rotate and Flip

题目大意:在二维平面上有(n)个点,给定(m)个操作,每个操作形如:

  • 点关于原点顺时针旋转(90)°.
  • 点关于原点逆时针旋转(90)°.
  • 点关于直线(x=p)的对称.
  • 点关于直线(y=p)的对称.

之后有(q)个询问,每个询问问一个指定的点(B)在执行了(A)次操作之后的坐标的值.特别的,如果询问的是第(0)次操作之后的位置,那么就是要求没有经过任何操作时的坐标.

数据范围:

(1 leq n leq 2 * 10^5)

(1 leq m leq 2 * 10^5)

(1 leq q leq 2 * 10^5)

(10^{-9} leq x_i,y_i leq 10^9)

(10^{-9} leq p leq 10^9)

思路

套路题,显然是要把所有的操作合并在一起直接对指定的坐标进行变换.考虑操作的表示和合并.一种初等做法是把坐标变换找出来并且发现本质是交换,对点加常数之后直接进行维护.另外一种更明了的做法是矩阵:

首先对于旋转操作,有比较套路的做法是套用旋转矩阵(egin{vmatrix}cos heta & -sin heta\sin heta & cos heta end{vmatrix}).前两种操作可以直接旋转矩阵套出来,比较显然.后面两种可以先把坐标代换找出来,也是比较简单,以第三种为例,也就是要让([x,y]*B=[2p-x,y]),不过这里有个问题,如果直接待定系数去求(B)矩阵那么会发现求出来的(B)矩阵是依赖(x)的取值的,那么如果依赖于(x)的取值,这个操作就不能合并了,还需要做一些处理,这里可以把([x,y])多加一个常数项变成([x,y,1]),那么再求就可以踢掉(x)的影响了,构造出来的矩阵就是(egin{vmatrix}-1 & 0 & 0\0 & 1 & 0 \2p &0 & 1 end{vmatrix}).对于第四种操作也是同理,那么顺次把所有操作以矩阵表示出来,可以发现对于求答案的这一步等价于是让([x,y,1])顺次去乘每个操作的矩阵,而后面这部分可以先预处理出来,也就是做一个前缀积的矩阵(op[i])表示前(i)个操作矩阵的前缀积,那么结果就是直接让([x,y,1])乘上(A)次操作后的矩阵就可以了.

当然由于拓展了坐标的列,所以旋转矩阵也需要额外多加一个(1)在右下角保证常数项不变.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
typedef pair<int,int> pii;
#define x first
#define y second

const int N = 3,_N = 2e5 + 7;
struct mat
{
	ll m[N][N];
};

pii a[_N];
mat op[_N];

mat mul(mat A,mat B,int len)
{
    mat c;memset(c.m,0,sizeof c.m);
    for(ll i = 0;i < len;++i)
        for(ll j = 0;j < len;++j)
            for(ll k = 0;k < len;++k)
            {
            	c.m[i][j]=(c.m[i][j]+(A.m[i][k]*B.m[k][j]));
            }
	return c;
}
mat qpow(mat a,ll len,ll k)
{
   	mat res;memset(res.m,0,sizeof res.m);
    for(ll i = 0;i < len;++i)   res.m[i][i] = 1;
    while(k)
    {
        if(k & 1)   res = mul(res,a,len);
        a = mul(a,a,len);
        k >>= 1;
    }
    return res;
}

int main()
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%d%d",&a[i].x,&a[i].y);
    int m;scanf("%d",&m);
    forn(i,1,m)
    {
    	int t;scanf("%d",&t);
    	mat _;memset(_.m,0,sizeof _.m);
    	if(t == 1)
    	{
    		_.m[0][1] = -1;_.m[1][0] = 1;_.m[2][2] = 1;
    		op[i] = mul(op[i - 1],_,3);
    	}
    	else if(t == 2)
    	{
    		_.m[0][1] = 1;_.m[1][0] = -1;_.m[2][2] = 1;
    		op[i] = mul(op[i - 1],_,3);
    	}
    	else if(t == 3)
    	{
    		int p;scanf("%d",&p);
    		_.m[0][0] = -1;_.m[1][1] = 1;_.m[2][0] = 2 * p;_.m[2][2] = 1;
    		op[i] = mul(op[i - 1],_,3);
    	}
    	else
    	{
    		int p;scanf("%d",&p);
    		_.m[0][0] = 1;_.m[1][1] = -1;_.m[2][1] = 2 * p;_.m[2][2] = 1;
    		op[i] = mul(op[i - 1],_,3);
    	}
    	if(i == 1)	op[i] = _;
    }
    int q;scanf("%d",&q);
    while(q--)
    {
    	int A,B;scanf("%d%d",&A,&B);
    	if(A == 0)	printf("%d %d
",a[B].x,a[B].y);
    	else
    	{
    		mat gamma;memset(gamma.m,0,sizeof gamma.m);
    		gamma.m[0][0] = a[B].x,gamma.m[0][1] = a[B].y,gamma.m[0][2] = 1;
    		gamma = mul(gamma,op[A],3);
    		printf("%lld %lld
",gamma.m[0][0],gamma.m[0][1]);
    	}
    }
    return 0;
}

F - Sugoroku2

题目大意:有(n+1)格子从(0)开始编号一开始你站在(0)位置目标是走到(n)位置或者更远.每一步在([1,m])之中等概率的选取一个数(j)并且向前移动(j)步如果到达目标或者更远则胜利.除此之外,有(k)个点存在陷阱,一旦走到某个有陷阱的点,那么就会直接被传送回(0)位置,问从起点走到终点的期望步数是多少.无解时输出(-1).

数据范围:

(1leq n leq 10^5)

(1 leq m leq 10^5)

(0 leq k leq 10)

(0 < A1 < A2 ... < Ak < N)

精度误差在(10^{-3})以内有效.

思路

期望dp套路:

  • 状态:(f[i])表示从(i)走到终点的期望步数
  • 入口:(f[n] = 0)
  • 转移:如果当前点(i)是一个危险点,那么(f[i] = f[0]),反之(f[i] = (f[i + 1] + f[i + 2] + ... + f[i + m]) / m + 1).
  • 出口:(f[0])

随后非常喜闻乐见的发现这是一个产生了环形依赖的表达式,关于这个形式的题目还有一道CF1453D这场我也写了题解.当然跟这个题没啥关系,只是模型一样.考虑做一个解方程的操作:设(f[0]=x).

那么通过设(f[0]=x)使之成为一个具体的数之后,环形依赖就可以被破掉了,就可以从后往前递推了,那么具体的方法就是从后往前维护一个长度为(m)的区间,这个区间维护两个值,一个是里面处在危险点的个数,一个是常数部分,因为从后往前递推最后推到(0)这个位置的时候,形式上来说(f[0]=Ax+B),这里的(A)就是后面危险点产生的,常数部分是后面除来的.这个等式最后可以写成(f[0] = B / (1-A)),那么当(A=1)的时候无解,其他的时候求出来就可以.

区间维护的就是(f[i + 1] + f[i + 2] +...+ f[i + m])这部分的(A,B)系数.

注意精度问题.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
typedef pair<double,double> pdd;
#define x first
#define y second

const int N = 1e5+7;
const double eps = 1e-6;
int a[N],dan[N];
pdd f[N];

bool cmp(double x,double y)
{
	if(fabs(x - y) < eps)	return 0;
	else if(x > y)	return 1;
	return -1;
}

int main()
{
	int n,m,k;scanf("%d%d%d",&n,&m,&k);
	forn(i,1,k)	scanf("%d",&a[i]),dan[a[i]] = 1;
	f[n] = {0,0};
	
	pdd last = {0,0};
	int l = n,r = n;
	while(l >= 1)
	{
		int i = l - 1;
		if(dan[i])	f[i] = {1,0};
		else
		{
			f[i].x = last.x / m;
			f[i].y = last.y / m + 1;
		}
		last.x += f[i].x;
		last.y += f[i].y;
		
		l = i;
		if(r - l + 1 > m)
		{
			last.x -= f[r].x;
			last.y -= f[r].y;
			--r;
		}
	}
	// forn(i,0,n)	cout << f[i].x << " " << f[i].y << endl;
	if(cmp(f[0].x,1.0) == 0)	puts("-1");
	else	printf("%lf",f[0].y / (1 - f[0].x));
    return 0;
}
原文地址:https://www.cnblogs.com/HotPants/p/14320191.html