队内训练(三)

Road To The 3rd Building - HDU_6827

传送门
思路

  • 先进行一遍处理前 n n n项和 s u m ( n ) sum(n) sum(n)
  • 对每个例子全部写一遍发现,每一个数都有特定的出现次数,在一段 n / 2 n/2 n/2的权中,第一个只贡献一次,第二次贡献两次,依此类推…
  • 所以可得每次的 c n t [ i ] = c n t [ i − 1 ] + ( s u m [ n − i + 1 ] − s u m [ i − 1 ] ) cnt[i]=cnt[i-1]+(sum[n-i+1]-sum[i-1]) cnt[i]=cnt[i1]+(sum[ni+1]sum[i1])
  • 对于分母, s u m n = ∑ i = 1 n n − i + 1 = ∑ i = 1 n i sumn=\sum_{i=1}^{n}n-i+1=\sum_{i=1}^{n}i sumn=i=1nni+1=i=1ni
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int N=2e5+10;
const int M=1e7+10;
ll fp(ll x,ll y){
    
    
	ll ans=1;
	while(y){
    
    
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll a[N],sum[N],cnt[N],inv[N];
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	int T;read(T);
	inv[1]=1;
	for(int i=2;i<=2e5+5;++i) inv[i]=mod-(mod/i)*inv[mod%i]%mod;
	while(T--){
    
    
		ll n;read(n);
		for(int i=1;i<=n;++i) read(a[i]);
		sum[0]=cnt[0]=0;
		for(int i=1;i<=n;++i) sum[i]=(sum[i-1]+a[i])%mod;
		int num=(n+1)/2;
		for(int i=1;i<=num;++i){
    
    
			cnt[i]=((cnt[i-1]+sum[n-i+1]-sum[i-1])%mod+mod)%mod;
		}
		ll ans=0;
		for(int i=1;i<=num;++i) ans=(ans+cnt[i]*inv[n+1-i]%mod)%mod;
		for(int i=num+1;i<=n;++i) ans=(ans+cnt[n-i+1]*inv[n+1-i]%mod)%mod;
		ll tmp=(1+n)*n/2;
		printf("%lld\n",ans*fp(tmp%mod,mod-2)%mod);
	}
	return 0;
}

Fragrant numbers-HDU_6831

传送门
思路

  • 由题目给出的样例可知,首先要对这段数列 1145141919 1145141919 1145141919进行5000以内的个数拆分,如 1 + 1 + 4 + 1+1+4+ 1+1+4+514便是如此
  • 后直接枚举区间,从len=2开始,求出 d p [ i ] [ i + l e n ] dp[i][i+len] dp[i][i+len]内所有符合条件的数
  • 因为数字在5000以内,所以该段数字的最后的几位最多加 19 + 11 , 9 + 11 19+11,9+11 19+11,9+11,就两位数字;所以对于所有数字而已,只需要在长度12以内就可以了
  • 进行区间 d p dp dp预处理后,直接记录答案输出即可,当然也可以用这段代码将表打出来,本来区间dp的代码能过,就249ms,意义不大
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int N=5e3+10;
const int M=1e4+10;
int ans[N];
int s[]={
    
    0,1,1,4,5,1,4,1,9,1,9,1,1,4,5,1,4,1,9,1,9};
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	set<int>dp[15][15];
	memset(ans,inf,sizeof ans);
	for(int i=1;i<=12;++i){
    
    
		int tmp=0;
		for(int j=i;j<=12;++j){
    
    
			tmp=tmp*10+s[j];
			if(tmp>5e3) break;
			dp[i][j].emplace(tmp);
			if(ans[tmp]>(j-i+1)){
    
    
				ans[tmp]=j-i+1;
			}
		}
	}
	for(int len=1;len<12;++len){
    
    
		for(int i=1;i+len<=12;++i){
    
    
			int j=len+i;
			for(int k=i;k<j;++k){
    
    
				for(auto u:dp[i][k]){
    
    
					for(auto v:dp[k+1][j]){
    
    
						int tmp=u+v;
						if(tmp<=5e3){
    
    
							dp[i][j].emplace(tmp);
						}
						tmp=u*v;
						if(tmp<=5e3){
    
    
							dp[i][j].emplace(tmp);
						}
					}
				}
			}
		}
	}
	for(int i=1;i<=12;++i){
    
    
		for(auto v:dp[1][i]){
    
    
			if(ans[v]>i) ans[v]=i;
		}
	}
	int T;read(T);
	while(T--){
    
    
		int n;read(n);
		printf("%d\n",ans[n]==inf?-1:ans[n]);
	}
	return 0;
}

A Very Easy Graph Problem-HDU_6832

传送门
思路

  • 这道题一开始卡在了如何选择边的问题上,却没有想到对于任意的 n n n,满足 ∑ i = 1 n − 1 2 i < 2 n \sum_{i=1}^{n-1}2^i<2^n i=1n12i<2n,所以这道题只要按照边输入的顺序构造最小生成树就行了,由于点比较多自然使用 k r u s k a l kruskal kruskal算法
  • 对后面的求权值问题,意思是求一条边 ( 左 边 的 0 × 右 边 的 1 + 左 边 的 1 × 右 边 的 0 ) × w (左边的0×右边的1+左边的1×右边的0)×w (0×1+1×0)×w,输入的时候先记录一遍总的0、1个数,后来进行树形 d p dp dp直接计算就好,详见代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int N=1e5+10;
const int M=2e5+10;
int head[N],cnt;
struct edge{
    
    
	int next,to;
	ll w;
}e[M];
void add(int u,int v,ll w){
    
    
	e[cnt].to=v;
	e[cnt].next=head[u];
	e[cnt].w=w;
	head[u]=cnt++;
}
int a[N],fa[N];
ll sum0,sum1,ans;
ll bit[N<<1];
pll dfs(int x,int fa){
    
    
	pll p={
    
    0,0};
	for(int i=head[x];~i;i=e[i].next){
    
    
		int v=e[i].to;
		if(v==fa) continue;
		pll tmp=dfs(v,x);
		ans=(ans+((sum0-tmp.first)*tmp.second+(sum1-tmp.second)*tmp.first)%mod*e[i].w%mod)%mod;
		p.first+=tmp.first;
		p.second+=tmp.second;
	}
	if(a[x]) ++p.second;
	else ++p.first;
	return p;
}
int get(int x){
    
    
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	int T;read(T);
	bit[0]=1;
	for(int i=1;i<=2e5;++i) bit[i]=bit[i-1]*2%mod;
	while(T--){
    
    
		memset(head,-1,sizeof head);cnt=sum0=sum1=0;
		int n,m;read(n),read(m);
		for(int i=1;i<=n;++i){
    
    
			read(a[i]);
			if(a[i]) ++sum1;
			else ++sum0;
		}
		for(int i=1;i<=n;++i){
    
    
			fa[i]=i;
		}
		for(int i=1;i<=m;++i){
    
    
			int x,y;
			read(x),read(y);
			int fx=get(x),fy=get(y);
			if(fx!=fy){
    
    
				fa[fy]=fx;
				add(x,y,bit[i]);
				add(y,x,bit[i]);
			}
		}
		ans=0;
		dfs(1,-1);
		printf("%lld\n",ans);
	}
	return 0;
}


Expectation-HDU_6836

传送门
思路

  • 一开始还不知道矩阵树这个算法, 只懂暴力求就直接懵了
  • 这题是使用矩阵树计算每一位的生成树个数,将每一位的期望值相加就是总答案
  • 对于原来的生成树总个数,需要对原图进行一次矩阵树求解操作
  • 剩下的基本就是矩阵树的板子题
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int N=1e2+10;
const int M=1e4+10;
ll a[N][N];
ll gauss(int n) {
    
    
	int sign = 0;
	ll ans = 1;
	for (int i = 1; i <= n; ++i) {
    
    
		for (int j = i + 1; j <= n; ++j) {
    
    
			int x = i, y = j;
			while (a[y][i]) {
    
    
				ll t = a[x][i] / a[y][i];
				for (int k = i; k <= n; ++k) 
					a[x][k] = (a[x][k] - a[y][k] * t) % mod;
				swap(x, y); 
			}
			if (x != i) {
    
    
				for (int k = 1; k <= n; k++) 
					swap(a[i][k], a[x][k]);
				sign ^= 1; 
			}
		}
		ans = ans * a[i][i] % mod;
	}
	if (sign) ans = -ans;
	if (ans < 0) ans += mod;
	return ans;
}
ll fp(ll x,ll y){
    
    
	ll ans=1;
	while(y){
    
    
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
int X[M],Y[M];
int w[M];
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	int T;read(T);
	while(T--){
    
    
		memset(a,0,sizeof a);
		int n,m;read(n),read(m);
		for(int i=1;i<=m;++i){
    
    
			read(X[i]);read(Y[i]);read(w[i]);
			++a[X[i]][X[i]];++a[Y[i]][Y[i]];
			a[X[i]][Y[i]]=--a[Y[i]][X[i]];
		} 
		ll down=fp(gauss(n-1),mod-2);
		ll ans=0;
		for(int i=0;i<=30;++i){
    
    
			memset(a,0,sizeof a);
			int tmp=1<<i;
			for(int i=1;i<=m;++i){
    
    
				if(w[i]&tmp){
    
    
					++a[X[i]][X[i]];
					++a[Y[i]][Y[i]];
					a[X[i]][Y[i]]=--a[Y[i]][X[i]];
				}
			}
			ans=(ans+tmp*gauss(n-1)%mod)%mod;
		}
		printf("%lld\n",ans*down%mod);
	}
	return 0;
}

Game-HDU_6850

传送门
思路

  • 能让自己赢的决定性的一步是走最长的那条路,这样后手就无路可走,以这一点为突破口,当删掉最长的边后只剩下最开始的点,那就说明先手无论怎么走,后手都能走最长的那一条,必定是 P P P点,反之则是 N N N
  • 往深处想,当有 n n n种长度不相同的边时,直接消去最长的相同长度的边,进入第 n − 1 n-1 n1种情况,一直删下去,最后都会回到最开始的上文所说的情况,直接根据删完所有边后是否还有第一个点来判断 N N N点还是 P P P
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int N=2e3+10;
const int M=1e7+10;
struct point{
    
    
	ll x,y;
	void input(){
    
    
		read(x);read(y);
	}
	ll friend operator+(const point &a,const point &b){
    
    
		return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
	}
}a[N];
struct edge{
    
    
	int u,v;
	ll w;
	edge(){
    
    }
	edge(int u,int v,ll w):u(u),v(v),w(w){
    
    }
}e[M];
bool vis[N];
int n,cnt;
bool check(){
    
    
	memset(vis,false,sizeof vis);
	for(int i=cnt;i>=1;){
    
    
		int pos=i;
		while(pos>1&&e[pos-1].w==e[pos].w) --pos;
		set<int>s;
		for(int j=pos;j<=i;++j){
    
    
			if(vis[e[j].u]||vis[e[j].v]) continue;
			s.emplace(e[j].u);
			s.emplace(e[j].v);
		}
		for(auto v:s){
    
    
			vis[v]=1;
		}
		i=pos-1;
		if(vis[1]) return 1;
	}
	return vis[1];
}
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	int T;read(T);
	while(T--){
    
    
		cnt=0;
		read(n);
		for(int i=1;i<=n;++i){
    
    
			a[i].input();
		}
		for(int i=1;i<=n;++i){
    
    
			for(int j=i+1;j<=n;++j){
    
    
				e[++cnt]=edge(i,j,(a[i]+a[j]));
			}
		}
		sort(e+1,e+1+cnt,[](const edge &a,const edge &b){
    
    
			return a.w<b.w;
		});
		if(check()) puts("YES");
		else puts("NO");
	}
	return 0;
}

Jogging-HDU_6853

传送门
思路

  • 一开始没看懂例子的分子是怎么算出来的,瞎琢磨了一番,后来才发现分子就直接只是在开始的点有多少种选择,包括留在原地这一种,当然还要进行特判,对于素数而言,在≤这个数的范围内只有与他自身 g c d   ! = 1 gcd\ !=1 gcd !=1,在方向上水平方向和竖直方向上随便遇上两个素数就停止了, k = − 1 k=-1 k=1也是,但在 k = 1 k=1 k=1的时候,当 x = y x=y x=y时,会出现无限向斜上方移动,直接将分子特判成0,就如样例①
  • 对于分母的值只需要进行一遍 b f s bfs bfs,并记录每一次可走的次数即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
template <typename T>
inline void read(T &x)
{
    
    
    int tmp = 1;char c = getchar();x = 0;
    while (c > '9' || c < '0'){
    
    if (c == '-')tmp = -1;c = getchar();}
    while (c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    x *= tmp;
}
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int N=1e2+10;
const int M=1e4+10;
ll ans1,ans2;
int dir[][2]={
    
    -1,-1,-1,0,-1,1,0,-1,0,1,1,-1,1,0,1,1};
void solve(){
    
    
	ans1=-1;ans2=0;
	map<pll,bool>vis;
	queue<pll>que;
	ll x,y;read(x),read(y);
	que.push({
    
    x,y});
	vis[{
    
    x,y}]=1;
	while(!que.empty()){
    
    
		pll p=que.front();
		que.pop();
		++ans2;
		for(int i=0;i<8;++i){
    
    
			ll xx=p.first+dir[i][0],yy=p.second+dir[i][1];
			if(xx==yy){
    
    
				ans1=0,ans2=1;
				return;
			}
			if((__gcd(xx,yy)==1)) continue;
			++ans2;
			if(vis[{
    
    xx,yy}]) continue;
			vis[{
    
    xx,yy}]=1;
			que.push({
    
    xx,yy});
		}
		if(ans1==-1) ans1=ans2;
	}
	ll tmp=__gcd(ans1,ans2);
	ans1/=tmp;ans2/=tmp;
}
int main(){
    
    
	ios::sync_with_stdio(false);cin.tie(nullptr); cout.tie(nullptr);
	int T;read(T);
	while(T--){
    
    
		solve();
		printf("%lld/%lld\n",ans1,ans2);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bloom_er/article/details/108024397