D - Logical Expression
题目大意:给定\(n\)个字符串,每个都是AND
或者OR
.要求你构造\(n+1\)个值,记入\(\{x0,x1,x2,...xn\}\).每个值只能取\(0/1\),同时根据你构造的\(x\)集合构造另外一个\(y\)集合:
- \(y0 = x0\)
- 对于\(i\geq 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\)
思路
套路题,显然是要把所有的操作合并在一起直接对指定的坐标进行变换.考虑操作的表示和合并.一种初等做法是把坐标变换找出来并且发现本质是交换,对点加常数之后直接进行维护.另外一种更明了的做法是矩阵:
首先对于旋转操作,有比较套路的做法是套用旋转矩阵\(\begin{vmatrix}cos\theta & -sin\theta\\sin\theta & cos\theta \end{vmatrix}\).前两种操作可以直接旋转矩阵套出来,比较显然.后面两种可以先把坐标代换找出来,也是比较简单,以第三种为例,也就是要让\([x,y]*B=[2p-x,y]\),不过这里有个问题,如果直接待定系数去求\(B\)矩阵那么会发现求出来的\(B\)矩阵是依赖\(x\)的取值的,那么如果依赖于\(x\)的取值,这个操作就不能合并了,还需要做一些处理,这里可以把\([x,y]\)多加一个常数项变成\([x,y,1]\),那么再求就可以踢掉\(x\)的影响了,构造出来的矩阵就是\(\begin{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\n",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\n",gamma.m[0][0],gamma.m[0][1]);
}
}
return 0;
}
F - Sugoroku2
题目大意:有\(n+1\)格子从\(0\)开始编号一开始你站在\(0\)位置目标是走到\(n\)位置或者更远.每一步在\([1,m]\)之中等概率的选取一个数\(j\)并且向前移动\(j\)步如果到达目标或者更远则胜利.除此之外,有\(k\)个点存在陷阱,一旦走到某个有陷阱的点,那么就会直接被传送回\(0\)位置,问从起点走到终点的期望步数是多少.无解时输出\(-1\).
数据范围:
\(1\leq 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;
}