预处理优化
一看就知道是快速幂,但是很可惜,暴力快速幂很慢,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 MAXN = 1e6 + 5;
struct edge
{
int next,to;
}e[MAXN<<1];
int head[MAXN<<1],cnt = 0;
int n,sum,ing[MAXN];
int vis[MAXN],ans[MAXN],lian[MAXN];
void add(int u,int v)
{
e[++cnt].next = head[u]; e[cnt].to = v; head[u] = cnt;
e[++cnt].next = head[v]; e[cnt].to = u; head[v] = cnt;
}
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 out(ll x)
{
if(x < 0) x = -x,pt('-');
if(x > 9) out(x/10);
pt(x % 10 + '0');
}
void dfs(int x)
{
vis[x] = 1;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].to;
if(vis[to]) continue;
dfs(to);
ans[x] += lian[to];
if(ans[x] > 1) lian[x] = 0;
if(!lian[to]) lian[x] = 0;
}
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
in(n); if(n == 2) {out(1); return 0;}
for(int i = 1;i < n;i++)
{
int x,y; in(x),in(y);
ing[x]++,ing[y]++; add(x,y);
}
for(int i = 1;i <= n;i++) lian[i] = 1;
for(int i = 1;i <= n;i++)
if(ing[i] > 1) {dfs(i); break;}
ll sum = 0;
for(int i = 1;i <= n;i++) if(ans[i]) sum += ans[i]-1;
out(sum);
return 0;
}