1. 程式人生 > >【NOIP2018模擬賽2018.11.1】

【NOIP2018模擬賽2018.11.1】

在這裡插入圖片描述

預處理優化

 一看就知道是快速冪,但是很可惜,暴力快速冪很慢,50分。

考慮分解b,達到O(1)查詢效果

 觀察到一個重點l <= 1012,即可知道b <= 1012
 於是考慮分解b,分成 x*1e6 + y 的形式,預處理出 a的1 ~ 1e6次方, 然後利用算出來的a1e6再預處理出a的1 * 1e6 ~ 1e6 * 1e6次方,這樣就可以拆成ax*1e6 * ay,進行O(1)查詢了。
詳情請看程式碼:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath> #include<string> #include<cstring> #include<map> #include<stack> #include<queue> #include<vector> using namespace std; #define ll long long #define gc getchar #define pt putchar #define ko pt(' ') #define ex pt('\n') const int MAXN = 1e6 + 5
; const int UP = 1e6; const int INF = 999999999; ll a,p,q,k,ans = -INF; ll b0,b1,l,m,c; ll Mul[MAXN],Mi[MAXN]; void in(ll &x) { ll num = 0,f = 1; char ch = gc(); while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();} while(ch >= '0' && ch <= '9') {num = (num<<3) +
(num<<1) + (ch-'0'); ch = gc();} x = num*f; } void out(ll x) { if(x < 0) x = -x,pt('-'); if(x > 9) out(x/10); pt(x % 10 + '0'); } ll quick_mi(ll a,ll b) { ll sum = 1; a %= p; // if(p == 999983) b %= (p-1); while(b) { if(b & 1) sum = sum * a % p; b >>= 1; a = a * a % p; } return sum % p; } void init() { Mul[0] = 1; Mi[0] = 1; for(int i = 1;i <= UP;i++) Mul[i] = Mul[i-1] * a % p; for(int i = 1;i <= UP;i++) Mi[i] = Mul[UP] * Mi[i-1] % p; } int main() { in(a),in(p),in(q),in(k); in(b0),in(l),in(m),in(c); init(); for(int i = 1;i <= q;i++) { b1 = (m * b0 % l + c) % l; ll now; if(b1 <= UP) now = Mul[b1]; else { ll t1 = b1/UP,t2 = b1%UP; now = Mul[t2]*Mi[t1] % p; } if(ans == -INF) ans = now; else ans ^= now; if(i % k == 0) out(ans),ex; b0 = b1; } return 0; }

在這裡插入圖片描述
在這裡插入圖片描述

線段樹+位運算亂搞

 挺噁心的這道題,關於1操作由於&操作不滿足結合律,更新只能暴力更新,3操作需要把式子展開合併找到合併方法才能運用進線段樹。
 由於1操作若全部暴力更新一定會T,但是更新只能暴力,於是我們用了一些玄學的位運算判斷對於哪一些k當前我們可以略過它不更新以起到優化的作用(如果資料專門卡的話會起不到任何優化,還得T,但這道題沒有卡,可以過)
 具體做法:
 1、首先,我們知道&運算結果一定 <= 原數,k可以更新它一定是k的二進位制數中有至少一位的 0 對準了 a(就是序列中一個數) 中的至少一位 1 。
 2、於是可以想到維護線段樹時將一個區間的所有 a |(或運算)起來,維護一個flag,代表a中的數1的分佈(如 al ~ r : 1000 0100 0010 ,或其來就是 1110 ,得到了所有可能分佈的1),用它去 & (~k)(相當於把k取反後看k中有沒有0對著flag中的1,舉個例子,若flag就等於前面那個,k為 1010, ~取反後變為0101,與上去得到 0100 ,不為0,則k可以更新這個區間),就可以判斷是否可以進行優化了,若最後不為0就暴力修改嘍。
3、算3那個式子有多種解法,lz的只是其中一種,各位隨意編寫。最後就像區間查詢一樣輸出就好了。
程式碼如下:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
#define ll long long
#define gc getchar
#define pt putchar
#define ko pt(' ')
#define ex pt('\n')
#define lx x << 1
#define rx x << 1 | 1 
const int MAXN = 1e5 + 5;
const int MOD = 998244353;
int n,q;
struct tree
{
	int len;
	ll sum,mul,hope,flag;
}t[MAXN<<2];
ll f(ll x) {x %= MOD; return x*x % MOD;} 
void in(int &x)
{
	int num = 0,f = 1; char ch = gc();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();}
	while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0'); ch = gc();}
	x = num*f;
}
void lin(ll &x)
{
	ll num = 0,f = 1; char ch = gc();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();}
	while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0'); ch = gc();}
	x = num*f;
}
void out(ll x)
{
	if(x < 0) x = -x,pt('-');
	if(x > 9) out(x/10);
	pt(x % 10 + '0');
}

ll calc(tree x,tree y)
{
	return (y.len*x.mul % MOD + x.len*y.mul % MOD + (((x.sum % MOD)*(y.sum % MOD) % MOD) << 1) % MOD) % MOD;
}

void build(int x,int l,int r)
{
	if(l == r){
		lin(t[x].sum);
		t[x].len = 1;
		t[x].flag = t[x].sum;
		t[x].mul = f(t[x].sum) % MOD;
		t[x].hope = f(t[x].sum<<1) % MOD;
		return;
	}
	int mid = (l + r) >> 1;
	build(lx,l,mid); build(rx,mid+1,r);
	t[x].sum = t[lx].sum + t[rx].sum;
	t[x].mul = (t[lx].mul + t[rx].mul) % MOD;
	t[x].len = t[lx].len + t[rx].len;
	t[x].flag = t[lx].flag | t[rx].flag;
	t[x].hope = (t[lx].hope + t[rx].hope + ((calc(t[lx],t[rx])<<1) % MOD)) % MOD;
}

void updata(int x,int l,int r,int L,int R,ll k)
{
	if(l == r && L <= l && r <= R){
		t[x].flag &= k;
		t[x].sum &= k;
		t[x].mul = f(t[x].sum) % MOD;
		t[x].hope = f(t[x].sum<<1) % MOD;
		return;
	}
	int mid = (l + r) >> 1;
	if(mid >= L && t[lx].flag&(~k))
		updata(lx,l,mid,L,R,k);
	if(mid < R && t[rx].flag&(~k)) 
		updata(rx,mid+1,r,L,R,k);
	t[x].sum = t[lx].sum + t[rx].sum;
	t[x].mul = (t[lx].mul + t[rx].mul) % MOD;
	t[x].flag = t[lx].flag | t[rx].flag;
	t[x].hope = (t[lx].hope + t[rx].hope + ((calc(t[lx],t[rx])<<1) % MOD)) % MOD;	
}

ll ask_sum(int x,int l,int r,int L,int R)
{
	if(L <= l && r <= R) return t[x].sum;
	ll ans = 0;
	int mid = (l + r) >> 1;
	if(L <= mid)
		ans += ask_sum(lx,l,mid,L,R);
	if(mid < R)
		ans += ask_sum(rx,mid+1,r,L,R);
	return ans;
}

void init(tree &x) {x.flag = 0,x.hope = 0,x.len = 0,x.mul = 0,x.sum = 0;}

tree ask_hope(int x,int l,int r,int L,int R)
{
	if(L <= l && r <= R) return t[x];
	tree ans,left,right;
	init(ans); init(left); init(right);
	int mid = (l + r) >> 1;
	if(L <= mid)
		left = ask_hope(lx,l,mid,L,R);
	if(mid < R)
		right = ask_hope(rx,mid+1,r,L,R);
	ans.sum = left.sum + right.sum;
	ans.mul = (left.mul + right.mul) % MOD;
	ans.len = left.len + right.len;
	ans.flag = left.flag | right.flag;
	ans.hope = (left.hope + right.hope + ((calc(left,right)<<1) % MOD)) % MOD;
	return ans;
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	in(n);
	build(1,1,n);
	in(q);
	while(q--)
	{
		int opt,l,r;
		in(opt),in(l),in(r);
		if(opt == 1)
		{
			ll k; lin(k);
			updata(1,1,n,l,r,k);
		}
		else if(opt == 2) out(ask_sum(1,1,n,l,r)),ex;
		else out(ask_hope(1,1,n,l,r).hope),ex;
	}
	return 0;
}

在這裡插入圖片描述
在這裡插入圖片描述

dfs + 用命分析

可以證明,若設每個節點的子樹數量為C,那麼每個節點至少有C-1顆子樹中要有信標。
 可以想到,若一個節點有兩顆子樹沒有信標的話,那麼至少那兩顆子樹的根到這個節點的距離相等了,於是不符合題意。
 有了這個結論了,就可以n2列舉根節點+dfs貪心選子樹較少的兒子(因為子樹越少就代表C越小,C-1就越小,答案就越小)放信標,可以得到70分。
 正解還得用到幾個不好想的性質:
1、首先是任意一個度數大於等於2的節點都可以當根,隨便選一個就能得到最優解了。
2、單獨處理鏈的情況,可以想到一條鏈(上面的點全是度數為2的點,也就是一個父親一個兒子)子樹數量一直為1,答案貢獻一直為0,直接用一個lian陣列判斷哪些點在鏈上,一條鏈最多用一個信標就行了。
3、還有判斷當前根的所有子樹是否滿足要求了,不需要再放根的條件,我也沒看懂
大家可以看程式碼理解一下。lz什麼時候看懂了再來補充這道題,至少70分暴力很好打吧。。
程式碼:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
#define ll long long
#define gc getchar
#define pt putchar
#define ko pt(' ')
#define ex pt('\n')
const int