【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