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[i−1]+(sum[n−i+1]−sum[i−1])
- 对于分母, 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=1nn−i+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=1n−12i<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 n−1种情况,一直删下去,最后都会回到最开始的上文所说的情况,直接根据删完所有边后是否还有第一个点来判断 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;
}