HDU多校2021第一场

HDU暑假多校第一场

[1001]Mod,_Or_and_Everything

解释

打表可以看出规律。

性质

  1. 设m=(n-1)/2
  2. (n mod i ) <= m,且当i <= m时,有 n mod (n-i) = i

结果就是 (n mod i) 取到 0~m 的所有整数,所有数或上就是 2 k − 1 2^k-1 2k1 。k是m的位数

代码

int tt; cin>>tt;
while(tt --){
    int n; cin>>n;
    int m = (n-1) / 2,res = 1;
    while(m) res <<= 1,m >>= 1;
    cout<<(res-1)<<endl;
}

[1005]Minimum_spanning_tree

解释

贪心,以2为根节点,所有质数都与2相连,贡献为 2*质数,其它所有数都与它的因数相连,则贡献为本身。

答案为 ( 3 + n ) ∗ ( n − 2 ) / 2 + ∑ i = 1 c n t p i (3 + n) * (n-2)/2 + \sum_{i=1}^{cnt}p_i (3+n)(n2)/2+i=1cntpi​​, p i p_i pi​​​是大于2的质数 ,预先处理质数。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e7 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;

int primes[N],cnt=0;
bool vis[N];

void ols(int n){
	for(int i=2;i<=n;i++){
		if(!vis[i]) primes[++cnt] = i;
		for(int j=1;j<=cnt&&primes[j]*i<=n;j++){
			vis[primes[j]*i] = true;
			if(i % primes[j] == 0) break;
		}
	}
}

signed main(){
	IOS
    ols(1e7);
    int tt; cin>>tt;
    while(tt --){
    	int n; cin>>n;
    	int ans = (3 + n) * (n - 2) / 2;
    	for(int i=2;i<=cnt && primes[i]<=n;i++) ans += primes[i];
    	cout<<ans<<endl;
	}

	return 0;
}

[1006]Xorsum

解释

处理异或前缀和,问题将区间转换为求两个点,枚举右端点,每次先询问是否存在最靠右的左端使得异或值大于等于k。之后再向01字典树中插入右端点。

如何询问?

假设处理到第i位,当前数为x

  1. k的这位为0,则只要x当前位互补的存在,则可以取互补的点为左端点,但是不互补的那位也可以继续往下走。
  2. k这位为1,只能走x当前为互补的那位。

如何插入?

对当前值进行分解插入到01字典树中,存下最后到当前节点的位置。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6 + 10;

int son[N][2],last[N],idx = 1;
int a[N];

void insert(int x,int pos){
	int p = 1;
	for(int i=29;i>=0;i--){
		int j = (x >> i) & 1;
		if(!son[p][j]) son[p][j] = ++idx;
		p = son[p][j];
		last[p] = pos;
	}
}

int query(int x,int y){
	int p = 1,res = -1;
	for(int i=29;i>=0;i--){
		int jx = (x >> i) & 1;
		int jy = (y >> i) & 1;
		if(!jy){ // k为0,互补的存在,则一定比k大,先存下res的大小,确定是否取这个
			if(son[p][jx^1]) res = max(res,last[son[p][jx^1]]);
			p = son[p][jx];
		}else p = son[p][jx^1];
		if(!p) break;
	}
	if(p != 0) res = max(res,last[p]);
	return res;
}

signed main(){
	IOS
    int tt; cin>>tt;
    while(tt --){
    	for(int i=1;i<=idx;i++) last[1] = -1,son[i][0] = son[i][1] = 0;
    	idx = 1;
    	insert(0,0);
    	int n,k; cin>>n>>k;
    	int ansl = 0,ansr = INF;
    	for(int i=1;i<=n;i++) cin>>a[i],a[i] ^= a[i-1];
    	for(int i=1;i<=n;i++){
    		int res = query(a[i],k);
    		if(res != -1 && (i - res) < (ansr - ansl)) ansr = i,ansl = res;
			insert(a[i],i);
		}
		if(ansr == INF) cout<<-1<<endl;
		else cout<<(ansl+1)<<" "<<ansr<<endl;
	}
	
	return 0;
}

[1007]Pass!

解释

知识点:推式子,特征方程,bsgs。

步骤

一、 推式子

f ( t ) f(t) f(t)为t秒到第一个人的方案数。考虑转移方程。

t − 1 t-1 t1秒时球一定不在第一个人手上

讨论 t − 2 t-2 t2秒时球在x手上:

  1. 假设在第一个人手上,则答案为 f ( t − 2 ) × ( n − 1 ) f(t-2)\times (n-1) f(t2)×(n1)​ 表示下一步传给除第一个人的位置再传到第一个人的方案数。
  2. 假设不在第一个人手上,假设下一步必然到达第一个位置,则方案数为 f ( t − 1 ) f(t-1) f(t1),剩下一秒,将这一步插到先前假设的下一步必然到达第一个位置中间,这一步可以到达除当前点,第一个点,则有 f ( t − 1 ) × ( n − 2 ) f(t-1)\times (n-2) f(t1)×(n2)

所以 f ( t ) = ( n − 2 ) × f ( t − 1 ) + ( n − 1 ) × f ( t − 2 ) f(t) = (n-2)\times f(t-1) + (n-1)\times f(t-2) f(t)=(n2)×f(t1)+(n1)×f(t2)

二、特征方程

作用就是将递推式子转换成通项公式。可以参考百度百科,参考资料

再结合 f ( 0 ) = 1 , f ( 1 ) = 0 f(0) = 1,f(1) = 0 f(0)=1,f(1)=0,求出两个解和系数,得到
f ( t ) = ( n − 1 ) t + ( n − 1 ) × ( − 1 ) t n f(t) = \frac{(n-1)^t+(n-1)\times (-1)^t}{n} f(t)=n(n1)t+(n1)×(1)t
将上面分奇数偶数考虑

奇数
f ( t ) = ( n − 1 ) t − ( n − 1 ) n f(t) = \frac{(n-1)^t-(n-1)}{n} f(t)=n(n1)t(n1)
偶数
f ( t ) = ( n − 1 ) t + ( n − 1 ) n f(t) = \frac{(n-1)^t+(n-1)}{n} f(t)=n(n1)t+(n1)
题目所求为
f ( t ) ≡ x m o d    ( p ) f(t) \equiv x\mod(p) f(t)xmod(p)
偶数变换
f ( t ) = ( n − 1 ) t + ( n − 1 ) n ⟶ ( n − 1 ) t ≡ ( n x − ( n − 1 ) + p ) % p m o d    ( p ) f(t) = \frac{(n-1)^t+(n-1)}{n} \longrightarrow (n-1)^t \equiv (nx-(n-1)+p)\%p \mod(p) f(t)=n(n1)t+(n1)(n1)t(nx(n1)+p)%pmod(p)
经典的求 A x ≡ B m o d    ( p ) A^x\equiv B\mod(p) AxBmod(p) ,用BSGS就行了

注意,当B为1时要特殊处理。

当对同一个x,t为偶数或者奇数都有解,则取最小。

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
typedef long long ll;

unordered_map<int,int> mp;

int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = (1ll * res * a) % mod;
		a = (1ll * a * a) % mod;
		b >>= 1;
	}
	return res;
}

int BSGS(int a,int b,int m){
	int s = b;
	for(int i=0;i<m;i++){
		mp[s] = i;
		s = (1ll * s * a) % mod;
	}
	a = qmi(a,m); s = 1;
	for(int i=0;i<=m;i++){
		int j = mp.count(s) != 0 ? mp[s] : -1;
		if(j != -1 && i*m-j >= 0) return i*m-j;
		s = (1ll * s * a) % mod;
	}
	return -1;
}

signed main(){
    int m = sqrt(mod)+1;
    int tt; scanf("%d",&tt);
    while(tt --){
    	int n,x; scanf("%d %d",&n,&x);
		int b1 = (1ll*n*x+(n-1)) % mod,a = n-1;
		if(b1 == 1){
			printf("1\n");
			continue;
		}
		mp.clear();
		int k1 = BSGS(a,b1,m);
		if(k1 == -1 || k1 % 2 == 0) k1 = INF;
		int b2 = (1ll*n*x-(n-1)+mod)%mod;
		if(b2 == 1){
			printf("0\n");
			continue;
		}
		mp.clear();
		int k2 = BSGS(a,b2,m);
		if(k2 == -1 || k2 % 2 == 1) k2 = INF;
		int ans = min(k1,k2);
		printf("%d\n",(ans==INF)?-1:ans);
	}
	return 0;
}

[1008]Maximal_submatrix

解释

法一

将每一行的每一个元素往下递推,存进dp数组里面, d p ( i , j ) dp(i,j) dp(i,j)表示第i行第j列这个元素连续前 d p ( i , j ) dp(i,j) dp(i,j) 行满足非递减。

之后对每一行处理, d p ( i , j ) dp(i,j) dp(i,j)​可以看出高,转换成了直方图中最大的矩形,用单调栈维护每一行。

法二

悬线法,转换成01矩阵(当前这个数比上面数大,则转换成1),统计全1矩阵的最大值.

  1. 滚动数组优化,不然会超内存
  2. 全一矩阵还应该包含多包含一行,因为全1矩阵的上一行全0也可以加入答案,注意特判当前的高等于行则不加1.

代码

单调栈

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;

stack<int> stk;
int dp[N][N],a[N][N];
int rmi[N],lmi[N];

void slove(int s,int m){
	for(int i=1;i<=m;i++) rmi[i] = m+1,lmi[i] = 0;
	while(stk.size()) stk.pop();
	for(int i=1;i<=m;i++){
		while(stk.size() && dp[s][stk.top()] > dp[s][i]) rmi[stk.top()] = i,stk.pop();
		stk.push(i);
	}
	while(stk.size()) stk.pop();
	for(int i=m;i>=1;i--){
		while(stk.size() && dp[s][stk.top()] > dp[s][i]) lmi[stk.top()] = i,stk.pop();
		stk.push(i);
	}
}

signed main(){
	IOS
    int tt; cin>>tt;
    while(tt --){
    	int n,m; cin>>n>>m;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			cin>>a[i][j];
    	for(int i=1;i<=m;i++) dp[1][i] = 1;
    	for(int i=2;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(a[i][j] >= a[i-1][j]) dp[i][j] = dp[i-1][j] + 1;
    			else dp[i][j] = 1;
    	int ans = 0;
    	for(int i=1;i<=n;i++){
    		slove(i,m);
    		for(int j=1;j<=m;j++){
    			int res = (rmi[j] - lmi[j] - 1) * dp[i][j];
    			ans = max(ans,res);
			}
		}
    	cout<<ans<<endl;
	}

	return 0;
}

悬线法

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;

int a[N][N],b[N][N];
int up[2][N],l[2][N],r[2][N];

signed main(){
	IOS
    int tt; cin>>tt;
    while(tt --){
    	int n,m; cin>>n>>m;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			cin>>a[i][j],b[i][j] = 0;
    			
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(a[i][j] >= a[i-1][j]) b[i][j] = 1;
    	
    	
    	int ans = 0;
    	for(int i=1;i<=n;i++){
    		
    		for(int j=1;j<=m;j++) l[i&1][j] = r[i&1][j] = j,up[i&1][j] = 1;
    		for(int j=2;j<=m;j++) if(b[i][j] && b[i][j-1]) l[i&1][j] = l[i&1][j-1];
    		for(int j=m-1;j>=1;j--) if(b[i][j] && b[i][j+1]) r[i&1][j] = r[i&1][j+1];
    		
    		for(int j=1;j<=m;j++){
    			if(b[i][j] && b[i-1][j]){
    				l[i&1][j] = max(l[i&1][j],l[(i-1)&1][j]);
    				r[i&1][j] = min(r[i&1][j],r[(i-1)&1][j]);
    				up[i&1][j] = up[(i-1)&1][j] + 1;
				}
				int maup = (up[i&1][j] == i) ? up[i&1][j] : up[i&1][j] + 1;
				ans = max(ans,(r[i&1][j] - l[i&1][j] + 1) * maup);
			}
		}
    	cout<<ans<<endl;
	}

	return 0;
}

[1009]KD-Graph

解释

用并查集维护连通块的数量,从小的边权开始,将两个点对应的集合合并成一个连通块。

有性质

  1. 答案一定是所有边权值里面的一个数,额外包含0
  2. 从最小边权开始枚举,一次性把所有相同的边权的点都进行并查集合并,每次有效合并一次则剩余连通块数量减一。
  3. 某次合并之后集合剩余的连通块数量等于k则为答案,小于k则不存在答案。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;

struct Node{
	int x,y,w;
	bool operator<(const Node&T)const{
		return w < T.w;
	}
}edge[N];

int fa[N];
int find(int x){
	if(x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}

signed main(){
	IOS
    int tt; cin>>tt;
    while(tt --){
    	int n,m,k; cin>>n>>m>>k;
    	for(int i=1;i<=n;i++) fa[i] = i;
    	for(int i=1;i<=m;i++) cin>>edge[i].x>>edge[i].y>>edge[i].w;
    	sort(edge+1,edge+m+1);
    	int cnt = n,last = 0,ans = -1;
    	for(int i=1;i<=m;i++){
    		if(edge[i].w == last){
    			int a = find(edge[i].x),b = find(edge[i].y);
    			fa[a] = b;
    			if(a != b) cnt --;
			}else{
				if(cnt < k) break;
				else if(cnt == k){
					ans = last;
					break;
				}
				last = edge[i].w;
				int a = find(edge[i].x),b = find(edge[i].y);
    			fa[a] = b;
    			if(a != b) cnt --;
			}
		}
    	cout<<ans<<endl;
	}
	return 0;
}

[1010]zoto

解释

将x,y分开看,在不考虑y轴的情况下,问题就变成了求区间 ( l , r ) (l,r) (l,r)中不同数的数量。这就是经典的问题,主席树,莫队,树状数组都可以维护这个。再对区间 ( l , r ) (l,r) (l,r)中不同数进行一个范围限制,主席树和树状数组就有些乏力(其实我没想到)。那就用莫队维护。

如何维护

  1. 对于x轴的维护,就是板子,主要修改add和remove函数,和增加y轴区间上的询问。
  2. 用sum[i]表示y方向上第i块里面有多少个不同的数,add和remove对sum进行维护,当维护完之后,对y区间询问。
  3. y区间询问操作,因为对y轴进行了很多区间划分,完整的块就直接加sum值。对于不完整,直接枚举判断。
  4. 时间复杂度大致为O( m n m\sqrt{n} mn ​​ )

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

inline int read(){
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'|| ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&& ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}

int a[N];
int blockx,blocky = 313;
int cnt[N],sum[N],ans[N];

struct Query{
	int lx,rx,pos;
	int ly,ry;
	
	bool operator <(const Query&T)const{
		return lx/blockx == T.lx/blockx ? rx < T.rx : lx < T.lx;
	}
	
}qr[N];

void add(int pos){
	if(++cnt[a[pos]] == 1) sum[a[pos]/blocky] ++;
}

void remove(int pos){
	if(--cnt[a[pos]] == 0) sum[a[pos]/blocky] --;
}

int query(int pos){
	int res = 0,len = pos/blocky;
	for(int i=0;i<len;i++) res += sum[i];
	for(int i=len*blocky;i<=pos;i++)
		res += (cnt[i] >= 1);
	return res;
}

signed main(){
    int tt; tt = read();
    while(tt --){
    	memset(sum,0,sizeof(sum));
    	memset(cnt,0,sizeof(cnt));
    	int n,m; n = read(),m = read();
	    blockx = sqrt(n);
	    for(register int i=1;i<=n;i++) a[i] = read();
	    for(register int i=1;i<=m;i++){
            qr[i].lx = read(),qr[i].ly = read(),qr[i].rx = read(),qr[i].ry = read();
            qr[i].pos = i;
        }
	    sort(qr+1,qr+m+1);
	    int curl = qr[1].lx,curr = qr[1].lx-1;
	    for(register int i=1;i<=m;i++){
	    	int l = qr[i].lx,r = qr[i].rx;
	    	while(curl < l) remove(curl++);
	    	while(curl > l) add(--curl);
	    	while(curr < r) add(++curr);
	    	while(curr > r) remove(curr--);
	    	ans[qr[i].pos] = query(qr[i].ry) - query(qr[i].ly-1);
		}
	    for(register int i=1;i<=m;i++) printf("%d\n",ans[i]);
	}
    
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值