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;
}