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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值