Atcoder ABC 189 題解


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM