牛客笔记
Wannafly挑战赛1
https://ac.nowcoder.com/acm/contest/15
A
给定一棵n个点的树,问其中有多少条长度为偶数的路径。路径的长度为经过的边的条数。x到y与y到x被视为同一条路径。路径的起点与终点不能相同。
分析:开始以为是个很复杂的dp 其实只需要将点按照深度分为奇数和偶数即可
设奇数点有a个 偶数点有b个
奇数与奇数 偶数与偶数 路径长度为偶数
即(a-1)×a/2+(b-1)×b/2
B
给定一个长度为n的整数数组,问有多少对互不重叠的非空区间,使得两个区间内的数的异或和为0。n<=1000
分析:很好想到前缀和 开始想着用容斥 发现不太好弄
互不重叠的区间 怎样才能统计出来
这也算个非常经典的问题吧
考虑一个端点i
将区间分成了 [1,i] [i+1,n]两部分 现在问题是要怎么设定统计规则使得不重不漏
设[1,i]为其中所有的区间 [i+1,n]为以i为左端点的所有区间
这样我们顺序枚举i 就可以不重不漏地将所有情况都考虑到
在顺序枚举的时候 会依次跟新[1,i]中的区间
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long LL;
const ll mod = 998244353;
const int DEG = 20;
const double PI = acos(-1.0);
const double eps = 1e-10;
const ll inf = 1e18;
const int N = 1e6 + 5;
int a[N];
int sum[N];
map<int, int> mp;
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] ^ a[i];
ll ans = 0;
for(int i = 1; i <= n; i++){
for(int j = 0; j < i; j++){
mp[sum[i] ^ sum[j]]++;
}
for(int j = i + 1; j <= n; j++){
ans += mp[sum[i] ^ sum[j]];
}
}
cout << ans << "\n";
return 0;
}
C
分析:
求所有点到点集中的距离最大值中的最小值
因为要求最大值中的最小值 所以先排除一些不可能为答案的最大值
将点集看作一个包围的圈 在圈外的点到点集的最大值一定 比 圈内的点到点集里面的最大值要大
综合圈内的点的最大值最小 想到一定是点集中最远的两点的中点!!!!!这个点很重要
问题转化为求点集中的最远两个点
考虑以1为根结点 对每个点赋予深度 此时点集中深度最深的一个点一定是最远的两点的其中一个
然后枚举另一个点 取所有距离最大即可
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=3e5+5;
void solve();
int n;
int fa[maxn][33],dp[maxn];
vector<int>Q[maxn];
void dfs(int,int);
int LCA(int,int);
int len(int,int);
int main(){
solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<n;i++){
int xx,yy;cin>>xx>>yy;
Q[xx].push_back(yy);
Q[yy].push_back(xx);
}
dp[1]=1;
dfs(1,0);
for(int j=1;j<33;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
int T;cin>>T;vector<int>G;
while(T--){
int xx;cin>>xx;
int ans=0,maxx=0,id;
G.clear();
for(int i=1;i<=xx;i++){
int uu;cin>>uu;
if(dp[uu]>maxx)maxx=dp[uu],id=uu;
G.push_back(uu);
}
for(int i=0;i<G.size();i++)
ans=max(ans,(len(id,G[i])+1)/2);
cout<<ans<<endl;
}
}
void dfs(int u,int f){
for(int i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(to==f)continue;
dp[to]=dp[u]+1;
fa[to][0]=u;
dfs(to,u);
}
}
int LCA(int u,int v){
if(dp[u]<dp[v])swap(u,v);
for(int i=32;i>=0;i--)
if(dp[fa[u][i]]>=dp[v])u=fa[u][i];
if(u==v)return u;
for(int i=32;i>=0;i--)
if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int len(int u,int v){
int lca=LCA(u,v);
return dp[u]+dp[v]-2*dp[lca];
}
D(二分图代补)
https://ac.nowcoder.com/acm/contest/15/D
E
分析:这道题目收获了好多
首先如果我们直接考虑边的权值异或和 也就是边里面任取组成鸽 但是问题是任取的边可能并不能只将点集分为两个集合(可能分为了更多的集合)
所以想办法直接找点 使之为集合S 剩下的为集合T
考虑将边权转换为点权 点权=该点直接连接的所有边的异或和
这样我们选择的点集S 所有点的异或和就是鸽的异或和
为什么?考虑如果边两端都在集合S内部 算点权的时候一定被算了2次 一端不在集合内部只被算了一次
现在问题变成了一些数 所有可能的异或和的和
考虑线性基 大小为sz
二进制考虑每一位产生的贡献 所有基中第i位为1的有a个 为0的有b个
第i位的贡献为 从a中选奇数个 与 b随便组合:
2^b{C(a,1)+C(a,3)+C(a,5)+... }= 2^b 2^(a-1) = 2 ^(a+b-1) = 2^(sz-1)
因为要求只输出前9位 高精度不现实 其实只用取ull即可
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define LL unsigned long long
#define Re register int
using namespace std;
const int N=1e5+3,M=2e6+3;
int n,m,x,y,z,o=1,head[N];LL ans;
struct QAQ{int w,to,next;}a[M<<1];
inline void add(Re x,Re y,Re z){a[++o].w=z,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
int f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=f?-x:x;
}
struct JI{
int cnt,p[33];
inline void insert(Re x){
for(Re i=30;i>=0;--i)
if((x>>i)&1){
if(!p[i]){++cnt,p[i]=x;break;}
x^=p[i];
}
}
}ji;
int cnt;
inline void print(LL x){
if(x>9)print(x/10);
putchar(x%10+'0');
if(++cnt>=9)exit(0);
}
int main(){
// freopen("cut.in","r",stdin);
// freopen("cut.out","w",stdout);
in(n),in(m);
for(Re i=1;i<=m;++i)in(x),in(y),in(z),add(x,y,z),add(y,x,z);
for(Re x=1;x<=n;++x){
Re v=0;
for(Re i=head[x];i;i=a[i].next)v^=a[i].w;
ji.insert(v);
}
for(Re i=0;i<=30;++i){
Re flag=0;
for(Re j=0;j<=30;++j)if(ji.p[j])flag|=((ji.p[j]>>i)&1);
if(flag)ans+=(LL)(1<<i)*(1<<(ji.cnt-1));
}
print(ans);
fclose(stdin);
fclose(stdout);
return 0;
}
Wannafly挑战赛9
https://ac.nowcoder.com/acm/contest/71/B
分析:
假如一个字符串的长度不是所有字符串中最短的,那么其答案一定为0
找出任意一个长度最短的字符串,用kmp算法把它和每个串进行匹配
求出它在每个串中出现了多少次,乘起来就是答案
所有长度最短的字符串答案都相同,不需要重复匹配
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5,MD=998244353;
string s[N];
int f[N+N];
int main()
{
int n,mi=N+N,ff,ans=1;
cin>>n;
for(int i=0; i<n; i++)
{
cin>>s[i];
if(s[i].size()<mi)mi=s[i].size(),ff=i;
}
for(int j=1; j<s[ff].size(); j++)
{
int k=f[j];
while(k&&s[ff][j]!=s[ff][k]) k=f[k];
f[j+1]=s[ff][j]==s[ff][k]?k+1:0;
}
for(int j=0; j<n; j++)
{
int cnt=0;
for(int k=0,l=0; k<s[j].size(); k++)
{
while (l&&s[ff][l]!=s[j][k]) l=f[l];
if (s[ff][l]==s[j][k]) l++;
if (l==mi)
cnt++;
}
ans=ans*1LL*cnt%MD;
}
for(int i=0; i<n; i++)
cout<<(s[i].size()==mi?ans:0)<<"\n";
return 0;
}
https://ac.nowcoder.com/acm/contest/71/C
分析:
用unsigned long long 让斐波拉契数列自然溢出 唯一性
#include<iostream>
#include<cstring>
using namespace std;
unsigned long long dp[100005];
char str[100005];
int main()
{
dp[1]=1;
dp[2]=2;
for(int i=3;i<=100000;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
while(cin >> str)
{
int n=strlen(str);
unsigned long long m=0;
for(int i=0;i<n;i++)
m=(m*10+str[i]-'0');
for(int i=1;i<=100000;i++)
if(dp[i]==m)
{
cout<<i<<endl;
break;
}
}
return 0;
}
https://ac.nowcoder.com/acm/contest/71/D
分析:
前面有m-1次入栈和m-k次出栈,后面有n-m次入栈和n-m+k次出栈,则可以两边先分别计算
求n次入栈m次出栈的合法排列次数则可参考卡特兰数公式,C(n+m,n)-C(n+m,m-1)
#include<bits/stdc++.h>
using namespace std;
const int MD=1e9+7,N=2e6+5;
int f[N],v[N];
int C(int n,int m)
{
if(m<0||m>n) return 0;
return 1LL*f[n]*v[m]%MD*v[n-m]%MD;
}
int F(int n,int m)
{
return (C(n+m,m)-C(n+m,m-1)+MD)%MD;
}
int main()
{
v[0]=v[1]=f[0]=f[1]=1;
for(int i=2; i<N; i++) f[i]=1LL*f[i-1]*i%MD,v[i]=1LL*v[MD%i]*(MD-MD/i)%MD;
for(int i=2; i<N; i++) v[i]=1LL*v[i-1]*v[i]%MD;
int n,m,k,T;
scanf("%d",&T);
while (T--)
{
scanf("%d%d%d",&n,&m,&k);
printf("%d\n",1LL*F(m-1,m-k)*F(n-m+k,n-m)%MD);
}
return 0;
}
https://ac.nowcoder.com/acm/contest/71/E
分析:
二进制拆分每一位
#include <bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
inline int read()
{int x=0,f=0;
char c=getchar();
while (c<'0'||c>'9')
{if (c=='-') f=1;
c=getchar();
}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
if (f) x=-x;
return x;
}
int a[100005],n,m,o[100005][21],p[100005];
int op[100005],l[100005],r[100005],x[100005];
int ans[100005];
// 一些差分约束的规则
// s[b]-s[a]<=k
// s[b]-s[a]>=k ----> s[a]-s[b]<=-k
// s[b]<=s[a]+k a-b:k
// s[a]<=s[b]-k b-a:-k
// s[a]-s[b]==k ---->s[a]-s[b]<=k && s[a]-s[b]>=k
// s[a]-s[b]<k ---->s[a]-s[b]<=k-1
// 前缀和dis 还要保证 dis[i]-dis[i-1]>=0
//如果数列s每项<=1 还要dis[i]-dis[i-1]<=1
int head[100005],nxt[300005],v[300005],w[300005],tot;
int dis[100005];
priority_queue<pair<int,int> > q;
inline void add(int a,int b,int val)
{tot++;nxt[tot]=head[a];head[a]=tot;v[tot]=b;w[tot]=val;}
inline void work(int o)
{memset(dis,0x3f,sizeof(dis));
dis[0]=0;
q.push(make_pair(0,0));
while (!q.empty())
{int t=(q.top()).second,d=-(q.top()).first;
q.pop();
if (dis[t]!=d) continue;
for (int i=head[t];i;i=nxt[i])
{if (dis[v[i]]>dis[t]+w[i])
{dis[v[i]]=dis[t]+w[i];
q.push(make_pair(-dis[v[i]],v[i]));
}
}
}
for (int i=1;i<=n;i++)
{int u=dis[i]-dis[i-1];
ans[i]+=u*(1<<o);
}
}
int main (){
int i,j;
memset(o,-1,sizeof(o));
n=read();m=read();
for (i=1;i<=m;i++)
{op[i]=read();l[i]=read();r[i]=read();x[i]=read();}
for (j=0;j<=19;j++)
{memset(head,0,sizeof(head));tot=0;
for (i=1;i<=n;i++)
{add(i-1,i,1);add(i,i-1,0);}
for (i=1;i<=m;i++)
{if (op[i]==1)
{int tp=x[i];
{if (tp&(1<<j)) {add(r[i],l[i]-1,-1);}
else {add(l[i]-1,r[i],0);}
}
}
else
{if (x[i]&(1<<j)) {add(r[i],l[i]-1,-(r[i]-l[i]+1));}
else {add(l[i]-1,r[i],r[i]-l[i]);}
}
}
work(j);
}
for (i=1;i<=n;i++)
{printf ("%d ",ans[i]);}
return 0;
}
codeJan与青蛙
https://ac.nowcoder.com/acm/contest/56/D
分析:
因为青蛙往前跳 所以我们倒着设计状态 设dp[i][k] 表示i到n 设置了k个的最小值
转移的时候顺带记录数量最大值即可
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=105;
void solve();
int n,m;
ll dp[maxn][maxn],pre[maxn],ans,maxx[maxn][maxn];
struct node{
ll id,a;
}Q[maxn];
bool cmp(node aa,node bb){
return aa.id<bb.id;
}
int main(){
int T;cin>>T;
while(T--)solve();
return 0;
}
void solve(){
cin>>n>>m;
ans=0;
memset(maxx,0,sizeof(maxx));
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>Q[i].id;
cin>>Q[i].a;
ans+=Q[i].a*Q[i].id;
}
sort(Q+1,Q+1+n,cmp);
for(int i=1;i<=n;i++)
pre[i]=pre[i-1]+Q[i].a;
for(int i=n;i>=1;i--){
dp[i][1]=-(pre[n]-pre[i-1])*Q[i].id;
maxx[i][1]=(pre[n]-pre[i-1]);
}
for(int k=2;k<=m;k++)
for(int ii=n;ii>=1;ii--)
for(int jj=n;jj>ii;jj--)
if(dp[ii][k]>dp[jj][k-1]-(pre[jj-1]-pre[ii-1])*Q[ii].id)
dp[ii][k]=dp[jj][k-1]-(pre[jj-1]-pre[ii-1])*Q[ii].id,maxx[ii][k]=max(maxx[jj][k-1],pre[jj-1]-pre[ii-1]);
else if(dp[ii][k]==dp[jj][k-1]-(pre[jj-1]-pre[ii-1])*Q[ii].id)
maxx[ii][k]=min(maxx[ii][k],max(maxx[jj][k-1],pre[jj-1]-pre[ii-1]));
ans+=dp[1][m];
cout<<ans<<" "<<maxx[1][m]<<endl;
}
https://ac.nowcoder.com/acm/contest/56/B
分析:
可以想象的是如果 m 足够大,codeJan 最后肯定会选择在相邻的两个城市来回走。
所以可以枚举两个相邻的城市(实际上应该是距离最小的两个城市)。并且直接” 奔向” 这两个城市的应该是最吼的!
但是还要考虑,可能先往后退到 一个城市,再” 奔向” 枚举的城市。这个点是最最关键的!!!!
举个例子就明白了:n = 3,m = 10, p = 2,三个城市的位置是 1 10 14。那么应该先退回到 1,然后再在 10 和 14 之间来回走。
时间复杂度:O(n)
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
int t,n,m,p,k;
const int N =1e5+5;
int a[N];
long long ans;
void work(int k,int w)
{
for (int i=k;i<=n;i++) if (i>1&&m-(i-k)>=0) ans=min(ans,(ll)w+a[i]-a[k]+((ll)a[i]-a[i-1])*(m-(i-k)));
for (int i=k-1;i;i--) if (i<n&&m-(k-i)>=0) ans=min(ans,(ll)w+a[k]-a[i]+((ll)(a[i+1]-a[i]))*(m-(k-i)));
}
int main()
{
cin>>t;
while(t--)
{
ans = 1e18;
cin>>n>>m>>p;
for(int i=1;i<=n;i++)
cin>>a[i];
for(k=0;a[k]<p&&k<=n;k++);
m--;
if (k<=n) work(k,a[k]-p);
if(k>1) work(k-1,p-a[k-1]);
cout<<ans<<endl;
}
return 0;
}
https://ac.nowcoder.com/acm/contest/56/C
分析:
如果x>=y,直接枚举氪金单数进行计算,否则不同的抽卡次数会对应不同的氪金单数,可以直接枚举抽卡次数进行计算,
只需要枚举足够多项即可达到要求的精度,标程枚举了 10^6项。
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long i,x,y;
double p,s=1,ans=0;
scanf("%lld%lld%lf",&x,&y,&p),p/=10000;
for(i=1;i<=1e7;i++)
{
ans+=s*p*(i*y/x+(i*y%x?1:0)),s*=(1-p);
if(s<1e-20)break;
}
printf("%.18lf\n",ans);
return 0;
}
小AA的数列
https://ac.nowcoder.com/acm/contest/35/B
一个非常牛逼的计数题目
题目大意:求所有区间长度为[L,R]并且长度为偶数的区间异或和之和
分析:
很明显要将每一位拆分分别考虑贡献
首先考虑没有任何限制 即求所有区间异或和
维护该位的前缀和1的奇偶性 统计的时候枚举每个右端点 找前面左端点奇偶性与之相反的个数 这个也用前缀和
现在考虑长度只有偶数 我们多开一维 分别统计奇数位置和偶数位置即可
现在考虑长度限制为[L,R] 方法还是枚举右端点i 找的左端点的范围从[1,i-1]变为[i-L+1,i-R+1] 还是用前缀和就可以
注意点:维护多维前缀和时 转移要每个状态都要往后转移
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10, P = 1e9+7;
int n,a[N],sum[2][2][N];
int main() {
int l,r;
scanf("%d%d%d", &n,&l,&r);
for (int i=1; i<=n; ++i) scanf("%d", &a[i]), a[i] ^= a[i-1];
int ans = 0;
for (int d=0; d<30; ++d) {
for (int i=0; i<=n; ++i) {
if (i) {
sum[0][0][i] = sum[0][0][i-1];
sum[0][1][i] = sum[0][1][i-1];
sum[1][0][i] = sum[1][0][i-1];
sum[1][1][i] = sum[1][1][i-1];
}
else sum[0][0][i] = sum[0][1][i] = sum[1][0][i] = sum[1][1][i] = 0;
++sum[i&1][a[i]>>d&1][i];
}
int ret = 0;
for (int i=l; i<=n; ++i) {
ret += sum[i&1][a[i]>>d&1^1][i-l];
if (i-r>0) ret -= sum[i&1][a[i]>>d&1^1][i-r-1];
}
ans = (ans+((1ll*ret)<<(1ll*d)))%P;
}
printf("%d\n", ans);
}
树的距离
https://ac.nowcoder.com/acm/contest/35/D
分析:乍一看 dsu on tree貌似也可以做 但是答案更巧妙
子树中到x的距离大于等于k 将所有点的距离都转化为到根的距离 dis[]
x的子树中的点y满足条件即dis[y]-dis[x]>=k
如果不考虑子树 统计只需要一个树状数组维护即可
那怎样才能满足均在x的子树中呢 我们知道一棵子树在dfs序里面一定是连续的一段 记录每个点x的子树范围[L,R]
转化到dfs序上 这样同样也是可以用树状数组即可
询问需要离线 对询问的dis大小排序
每次将dis大于当前询问点的都塞进树状数组中 询问子树中树状数组的dfs序[L,R]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int head[maxn],tot,l[maxn],r[maxn];
ll dis[maxn];
struct Num{
ll val;int pos;
}d[maxn];
struct node{
int v,nxt;ll w;
}edge[maxn<<1];
struct Query{
int id,l,r;
ll w,val;
}Q[maxn];
void init() { tot=0,memset(head,-1,sizeof(head)); }
void add( int u,int v,ll w )
{
edge[tot].v=v;edge[tot].nxt=head[u];edge[tot].w=w;
head[u]=tot++;
}
bool cmp1( Query a,Query b ) { return a.val>b.val; }
bool cmp2( Num a,Num b ) { return a.val>b.val; }
int tim;
void dfs( int u,int f )
{
d[u].val=dis[u];
d[u].pos=l[u]=++tim;
for( int i=head[u];~i;i=edge[i].nxt )
{
int v=edge[i].v,w=edge[i].w;
if( v==f ) continue;
dis[v]=dis[u]+w;
dfs(v,u);
}
r[u]=tim;
}
#define lowbit(x) x&(-x)
ll sum[maxn],cnt[maxn];
void add( int x ,ll c ) { while( x<maxn) sum[x]+=c,cnt[x]++,x+=lowbit(x); }
ll query( int x,bool op ) {ll res1=0,res2=0;while( x ) res1+=sum[x],res2+=cnt[x],x-=lowbit(x); return !op ? res1 : res2 ; }
ll ans[maxn];
int main()
{
int n;
scanf("%d",&n);
init();
for( int i=2;i<=n;i++ )
{
int v,val;
scanf("%d%d",&v,&val);
add(i,v,val);add(v,i,val);
}
dfs(1,0);
int q;
scanf("%d",&q);
for( int i=1;i<=q;i++ )
{
int x,w;
scanf("%d%d",&x,&w);
Q[i].id=i;
Q[i].w=d[x].val;
Q[i].val=d[x].val+w;
Q[i].l=l[x];
Q[i].r=r[x];
}
sort(Q+1,Q+1+q,cmp1);
sort(d+1,d+1+n,cmp2);
int j=1;
for( int i=1;i<=q;i++ )
{
while( d[j].val>=Q[i].val && j<=n )
{
add(d[j].pos,d[j].val);
++j;
}
ans[Q[i].id]=query(Q[i].r,0)-query(Q[i].l,0);
ans[Q[i].id]-=query(Q[i].r,1)*Q[i].w;
ans[Q[i].id]+=query(Q[i].l,1)*Q[i].w;
}
for( int i=1;i<=q;i++ ) printf("%lld\n",ans[i]);
}
珂学送分
https://ac.nowcoder.com/acm/contest/20/A
分析:
这里用单指针转移的方法真的太牛逼了!!!!
#include <bits/stdc++.h>
using namespace std;
int a[100005];
double dp[100005];
int n,m;
void solve(int i)
{
dp[n]=1.0;
int sum=a[n],r=n;
double sumc=0;
for (int q=n-1;q>0;q--)
{
sum+=a[q];
sumc+=dp[q+1];
while (sum>i)
{
sum-=a[r];
sumc-=dp[r+1];
r--;
}
dp[q]=sumc/(1.0*(r-q+1))+1;
}
printf("%.2lf\n",dp[1]);
}
int main()
{
int mx=0;
scanf("%d %d",&n,&m);
for (int q=1;q<=n;q++)
{
scanf("%d",&a[q]);
mx=max(mx,a[q]);
}
int k;
for( int q=0;q<m;q++)
{
scanf("%d",&k);
if (k<mx) printf("YNOI is good OI!\n");
else solve(k);
}
return 0;
}
位数差
https://ac.nowcoder.com/acm/contest/20/C
分析:
开始想法是倒推 但是要支持 有序数组 插入和查询 不会
换种想法 设bit(x)表示x的位数 结果为Σbit(a+b) - Σbit(a)
我们将两部分拆开来算 -Σbit(a)是可以固定算出来的
Σbit(a+b)表示的是任意两个数相加的位数和
那么我们可以先排序 这样的好处是 当我们再次遍历i的时候
对每一个i ai+aj位数一定是单调的 这样就可以用lowerbound算出来
很巧妙的一个题目!!!
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int a[maxn];
long long mi[maxn];
int bit(int num)
{
if(num == 0) return 1;
int ret = 0;
while(num) num /= 10, ret++;
return ret;
}
int main()
{
mi[0] = 0, mi[1] = 1;
for(int i = 2; i <= 10; i++) mi[i] = mi[i -1] * 10;
int n;
scanf("%d", &n);
long long ans = 0;
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
ans -= 1LL * bit(a[i]) * (n - i - 1);
}
sort(a, a + n);
for(int i = 0; i < n; i++)
{
for(int j = 0; j <= 9; j++)
{
int lb = lower_bound(a + i + 1, a + n, mi[j] - a[i]) - a;
int rb = lower_bound(a + i + 1, a + n, mi[j + 1] - a[i]) - a;
long long num = rb - lb;
if(j) ans += 1LL * j * num;
else ans += 1LL * 1 * num;
}
}
printf("%lld\n", ans);
return 0;
}
小H和密码
分析:
题目上虽然说给出长度不超10000的字符串 但是实际上如果长度大于N了一定不能被拼出来
剩下的问题就简单了 设dp[i][j]表示用到前i个密码 已经拼出询问字符的前j个是否可行 dp=1表示能行 dp=0表示不行
转移的话如果第i个密码里面含有询问字符第j个字符 如果有的话dp[i][j]|=dp[i-1][j-1]
如果第i个密码里面含有空字符 dp[i][j]=dp[i-1][j]
剩下的就是一些初始化细节问题
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=305;
int n,m,Q;
string s;
int dp[maxn][maxn];
int pd[maxn][27];
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n>>m>>Q;
for(int i=1;i<=n;i++){
cin>>s;
for(int j=0;j<m;j++)
if(s[j]!='#')
pd[i][s[j]-'a']=1;
else pd[i][26]=1;
}
while(Q--){
cin>>s;int len=s.size();
if(len>n){
cout<<"NO"<<endl;
continue;
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=len;j++){
if(j>=1&&pd[i][s[j-1]-'a'])
dp[i][j]|=dp[i-1][j-1];
if(pd[i][26])
dp[i][j]|=dp[i-1][j];
}
if(dp[n][len])cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
小H和游戏
分析:
非常巧妙的一题
这个题目关键点在于只会波及距离2范围以内的
考虑怎么O(1)就能够求出来 每个点只有一个父亲一个爷爷
设dp[x][0]表示向下能拓展距离为0的个数
dp[x][1]表示向下能扩展距离为1的个数
dp[x][2]表示向下能扩展距离为2的个数
为什么这样表示?
这样兄弟的贡献就可以通过父亲向下转移过来
当我们跟新一个点x的时候
dp[x][0]++ dp[x][1]++ dp[x][2]++ 表示x可以向下扩展距离为0和1和2的点++
dp[fa[x]][0]++,dp[fa[x]][1]++表示x的父亲向下扩展距离为0和1的点++
dp[fa[fa[x]]][0]++表示x的爷爷能向下扩展距离为0的点++
询问时dp[x][0]+dp[fa[x]][1]+dp[fa[fa[x]]][2];
但是这样会算重复 因为dp[fa[x]][1]中和dp[x][0]中都含有轰炸x的贡献
所以再开一个数组G[x]表示当前x被轰炸了多少次
每次询问正确答案为dp[x][0]+dp[fa[x]][1]+dp[fa[fa[x]]][2]-G[x]
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=750000+5;
int fa[maxn];
int n,q,x,y;
vector<int>Q[maxn];
int dp[maxn][3],G[maxn];
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void dfs(int u,int f){
fa[u]=f;
for(int i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(to==f)continue;
dfs(to,u);
}
}
void solve(){
cin>>n>>q;
for(int i=2;i<=n;i++){
cin>>x>>y;
Q[x].push_back(y);
Q[y].push_back(x);
}
dfs(1,0);
while(q--){
cin>>x;G[x]++;
dp[x][1]++;dp[x][2]++;dp[x][0]++;
dp[fa[x]][0]++;dp[fa[x]][1]++;
dp[fa[fa[x]]][0]++;
cout<<dp[x][0]+dp[fa[x]][1]+dp[fa[fa[x]]][2]-G[x]<<endl;
}
}
小H的询问
分析:
线段树 对于每个线段树节点
维护lm(左端点最长向右扩展) rm(右端点最长向左扩展) mm(区间里面最长的权值和) s(整个区间的权值和(如果不满足的话为负无穷大))
关键在于merge操作
对于两个线段树节点a,b合并为新节点c
首先 c.lm=a.lm c.rm=b.rm c.mm=max(a.mm,b.mm)
如果合并的交界处满足题意的话
c.mm=max(c.mm,a.rm+b.lm)
c.lm=max(a.lm,a.s+b.lm) c.rm=max(b.rm,b.s+a.rm)
c.s=max(负无穷,a.s+b.s)
都挺好理解的
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int Maxn=100000+50;
struct Node{
int l,r;
ll lm,rm,mm,s;
}a[Maxn*4];
int Val[Maxn];
int n,m;
Node merge(Node a,Node b)
{
Node c;
c.lm=a.lm;c.rm=b.rm;
c.mm=max(a.mm,b.mm);c.s=-1e18;
if((Val[a.r]+Val[b.l])%2)
{
c.mm=max(a.rm+b.lm,c.mm);
c.lm=max(a.lm,a.s+b.lm);
c.rm=max(b.rm,b.s+a.rm);
c.s=max((ll)-1e18,a.s+b.s);
}
c.l=a.l;c.r=b.r;
return c;
}
void build(int id,int l,int r)
{
if(l==r)
{
a[id].l=a[id].r=l;
a[id].lm=a[id].rm=a[id].mm=a[id].s=Val[l];
return;
}
int mid=(l+r)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
a[id]=merge(a[id*2],a[id*2+1]);
}
void update(int id,int l,int r,int k,int v)
{
if(l==r)
{
a[id].lm=a[id].rm=a[id].mm=a[id].s=v;
return;
}
int mid=(l+r)/2;
if(k<=mid)
update(id*2,l,mid,k,v);
else
update(id*2+1,mid+1,r,k,v);
a[id]=merge(a[id*2],a[id*2+1]);
}
Node query(int id,int l,int r,int L,int R)
{
if(L<=l && R>=r)
return a[id];
int mid=(l+r)/2;
if(R<=mid)
return query(id*2,l,mid,L,R);
if(L>mid)
return query(id*2+1,mid+1,r,L,R);
return merge(query(id*2,l,mid,L,R),query(id*2+1,mid+1,r,L,R));
}
int main(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&Val[i]);
build(1,1,n);
while(m--)
{
int t,l,r;
scanf("%d%d%d",&t,&l,&r);
if(t==1)
{
Val[l]=r;
update(1,1,n,l,r);
}
else
printf("%lld\n",query(1,1,n,l,r).mm);
}
return 0;
}
删除子串
https://ac.nowcoder.com/acm/contest/79/C
分析:
我们分别设a[i]和b[i]表示当前变化次数为i 以a结尾的最长长度和 以b结尾的最长长度
按顺序遍历 每次加入一个字符 我们就重新更新一遍
a[j]=max(a[j],b[j-1])+1
b[j]=max(b[j],a[j-1])+1
特别地 a[0]每次++ 因为j-1此时为负数
#include<cstdio>
char str[100005];
int a[11],b[11];
int main(){
int n,m,k=0;
scanf("%d%d%s",&n,&m,str);
while(str[k]=='b')k++;
for(;k<n;k++){
if(str[k]=='a'){
a[0]++;
for(int j=1;j<=m;j++)a[j]=(a[j]>b[j-1]?a[j]:b[j-1])+1;
}
else {
for(int j=1;j<=m;j++)b[j]=(b[j]>a[j-1]?b[j]:a[j-1])+1;
}
}
int res=a[0];
for(int i=1;i<=m;i++){
res=res>a[i]?res:a[i];
res=res>b[i]?res:b[i];
}
printf("%d\n",res);
}
Matches
https://ac.nowcoder.com/acm/contest/57355/H
分析:
我们将点对 a<b 的点对作为一类点 a>b 的点对作为二类点
如果要交换的话 一类点内部一定不会交换 二类点内部一定不会交换 一二类点没有交一定不会换
总而言之 只有一二类点有交集才会交换 这样才能使得答案更小
问题转化为一类点二类点的交最大
我们将所有点对按照右端点升序排序 这样我们之前遍历的区间右端点一定大于后面的右端点
然后我们倒着遍历 l[0]表示此时一类点的最左端 l[1]表示此时二类点的最左端 每次不断跟新
假设我们此时遍历到一段区间[L,R] 我们强制重叠区间的右端点为R 很明显左端点就是max(L,l[])
非常牛的一道区间题目!!!!
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
#define ll long long
int m,n,l[2],len,x[N],y[N]; ll ans;
struct node{
int a,b,t;
bool operator < (const node &a)const{return b<a.b;}
} p[N];
int main(){
cin>>m,l[0]=l[1]=2e9;
for(int i=1;i<=m;++i) scanf("%d",x+i);
for(int i=1;i<=m;++i) scanf("%d",y+i);
for(int i=1,a,b;i<=m;++i){
a=x[i],b=y[i];
if(a==b) continue;
++n; if(a>b) p[n].t=1,swap(a,b);
p[n].a=a,p[n].b=b,ans+=b-a;
}
sort(p+1,p+n+1);
for(int i=n,val;i>=1;--i){
int tp=p[i].t;
if(l[tp^1]<p[i].b) len=max(len,p[i].b-max(p[i].a,l[tp^1]));
l[tp]=min(l[tp],p[i].a);
}
cout<<ans-2*len;
}
Water
https://ac.nowcoder.com/acm/contest/57355/M
分析:
问题可以转化为Z=aX+bY
很明显可以判断出如果Z|gcd(X,Y)则一定有解 其中X Y可以用exgcd求出一组特解
很明显我们要分情况讨论
假设a>b(a<b同理)
(1)X>0,Y>0 ans=2X+2Y=2|x|+2|y|
(2)X>0,Y<0 ans=4|y|+2(x-|y|)-1=2|x|+2|y|-1
X<0,Y<0 和 X<0,Y>0 这两种情况是不成立的
因为上面我们求出来的是特解 通解为 X+bt 和 Y-at
这里面只有t为未知数
问题转化为求|X+bt|+|Y-at|的最小值
来复习一下高中的双绝对值不等式
图像画出来求-X/b,Y/a 两点附近的前后判断即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b,c,d,x,y,res;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1,y=0;
return a;
}
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
void ck(ll tt){
ll r=x+b*tt,s=y-a*tt;
if(r>=0&&s>=0)
res=min(res,2*(r+s));
else res=min(res,2*abs(r-s)-1);
}
void solve(){
cin>>a>>b>>c;
if(a==b){
if(c%a==0)cout<<2*c/a<<endl;
else cout<<"-1"<<endl;
return;
}
d=exgcd(a,b,x,y);
if(c%d){
cout<<-1<<'\n';
return;
}
a/=d;
b/=d;
c/=d;
x=(x*c%b+b)%b;
y=(c-x*a)/b;
res=1e18;
double t;ll tt;
t=(double)x/(double)b;t=-t;
tt=t;
ck(tt);ck(tt-1);ck(tt+1);
t=(double)y/(double)a;
tt=t;
ck(tt);ck(tt-1);ck(tt+1);
cout<<res<<'\n';
}
int main()
{
int t;
cin>>t;
while(t--){
solve();
}
}
鸽天的放鸽序列
https://ac.nowcoder.com/acm/contest/1115/B
分析:
首先考虑什么情况下权值最大 很明显让每一个0的前面1的个数都是奇数个 这样一定是最优的
方案变为 将所有的1展开 0插入到里面奇数位置的方案数
也就是 n-k个相同的球放入(k+1)/2个不同的桶内 允许有空桶 C(N+K-1,K-1)
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int mod=1e9+7;
ll n,k;
ll fast_mi(ll aa,ll bb){
ll res=1;
while(bb){
if(bb&1)res=res*aa%mod;
bb>>=1;
aa=aa*aa%mod;
}
return res;
}
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n>>k;
if(k==0){
cout<<1<<endl;
return;
}
ll res=1,p=1;
ll N=n-k,K=(k+1)/2;//n-k个相同的球放入(k+1)/2个不同的桶内 C(N+K-1,K-1)
for(ll i=1;i<=N+K-1;i++)res=res*i%mod;
for(ll i=1;i<=N;i++)p=p*i%mod;
for(ll i=1;i<=K-1;i++)p=p*i%mod;
cout<<res*fast_mi(p,mod-2)%mod;
}
字符串
https://ac.nowcoder.com/acm/contest/3782/B
分析:
分情况讨论 关键点在于要将特殊的情况列出来 看似题目很简单 实际不简单
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main(void) {
int T; scanf("%d",&T);
ll n,m;
while (T--) {
scanf("%lld%lld",&n,&m);
if (n <= m) {
if (m % n == 0) printf("Yes\n");
else printf("No\n");
} else {
if (n%(n-m) == 0) printf("No\n");
else printf("Yes\n");
}
}
return 0;
}
2023牛客暑假2
The Game of Eating
分析:
考虑倒着分析 如果剩下最后一个人 一定是选当前能选的最大值 倒数第二就是排除倒数第一选的剩下的最大值
所以依次模拟就完事了
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=2e3+5;
struct node{
int val,id;
}Q[maxn][maxn];
bool cmp(node aa,node bb){
return aa.val>bb.val;
}
bool vis[maxn];
int n,m,k,now;
int main(){
int T;cin>>T;
while(T--)solve();
return 0;
}
void solve(){
memset(vis,0,sizeof(vis));
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cin>>Q[i][j].val,Q[i][j].id=j;
sort(Q[i]+1,Q[i]+1+m,cmp);
}
while(k){
now=k%n;
if(now==0)now=n;
for(int i=1;i<=m;i++)
if(!vis[Q[now][i].id]){
vis[Q[now][i].id]=1;
break;
}
k--;
}
for(int i=1;i<=m;i++)
if(vis[i])cout<<i<<" ";
cout<<endl;
}
Link with Chess Game
分析:开始以为是相对位置 后面才发现只要坐标位置不同就不同
考虑找规律 爆搜打前几个的表 然后就发现了规律 过的时候都有点难以置信
后面看题解才知道这是个非常经典的二分图博弈问题
把三颗棋子看作是三个维度,显然所有状态组成的图是一个正方体,一定是二分图
在 n 为偶数的情况下,最大匹配可以覆盖所有状态,即初始状态一定在某一对匹配上。这种情况下,先手一定落在匹配中的最后一个点,因此是必胜的
在 n 为奇数的情况下,对于起始点在较小一部的状态点,一定在某一最大匹配上。这种情况下,先手一定落在匹配中的最后一个点,因此是必胜的
对于起始点在较大一部中的状态点的情况,一定存在一个最大匹配不包含起始点。因此在这个最大匹配上,后手一定落在匹配中的最后一个点,因此是必败的
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
int main(){
int T;cin>>T;
while(T--)solve();
return 0;
}
void solve(){
int n,x,y,z;
cin>>n>>x>>y>>z;
if(n&1){
if((x+y+z)&1)cout<<"Bob"<<endl;
else cout<<"Alice"<<endl;
}
else cout<<"Alice"<<endl;
}
0 and 1 in BIT
分析:
首先有个关键的性质 取反+1 等价于 -1再取反!!!!! 同理 取反+X 等价于 -X再取反
所对于B操作来说 前面有奇数个A 等价于-1 前面有偶数个A 就是+1
因为是询问多段操作 所以考虑用到前缀和分别维护B前有奇数个A的B的个数 和B前有偶数个A的B的个数
对于每次询问 只要查询 区间两端 两种B的类型 分别有多少个即可
特别的 因为我们只是维护了奇偶性 所以还需要判断区间左端点前如果有奇数个A 那么区间内部的两种类型会交换
如果左端点前A的个数是偶数 不产生影响
加上贡献 注意这里加减都需要取模
最后需要判断区间内部的A的个数 如果是奇数则最后需要取一次反 如果是偶数就不需要了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
using namespace std;
const int N = 200015;
const int M = 100;
typedef long long lld;
char opera[N];
int n, q, oper[N], cnt;
lld c[2][N], ans = 0;
char ss[M];
lld ll, rr;
int l, r;
bool flip = false;
#define int lld
int lowbit(int x) {
return x & (-x);
}
void add(int x, lld k) {
if(k > 0) for(; x <= n; x += lowbit(x)) c[0][x] += k;
else for(; x <= n; x += lowbit(x)) c[1][x] += k;
}
lld sum_pos(int x) {
lld tot = 0;
for(; x; x -= lowbit(x)) tot += c[0][x];
return tot;
}
lld sum_neg(int x) {
lld tot = 0;
for(; x; x -= lowbit(x)) tot += c[1][x];
return tot;
}
lld getd() {
lld tmp = 0;
for(int i = 0; i < cnt; i++) {
tmp = tmp * 2 + (ss[i] - '0');
}
return tmp;
}
void getans() {
for(int i = 0; i < cnt; i++) {
if(((ans >> (cnt - i - 1))) & 1) ss[i] = '1';
else ss[i] = '0';
}
if(flip) {
for(int i = 0; i < cnt; i++) {
if(ss[i] == '1') ss[i] = '0';
else ss[i] = '1';
}
}
}
signed main() {
cin >> n >> q;
cin >> (opera + 1);
int nowopt = 1;
for(int i = 1; i <= n; i++) {
if(opera[i] == 'A') {
nowopt *= -1;
oper[i] = 1;
} else if(opera[i] == 'B') {
add(i, nowopt);
}
oper[i] += oper[i - 1];
}
while(q--) {
cin >> ll >> rr;
cin >> ss;
cnt = strlen(ss);
l = min((ans ^ ll) % n + 1, (ans ^ rr) % n + 1);
r = max((ans ^ ll) % n + 1, (ans ^ rr) % n + 1);
ans = getd();
lld mod = (1ll << (cnt));
int pera = oper[l - 1];
lld pos = sum_pos(r) - sum_pos(l - 1);
lld neg = sum_neg(r) - sum_neg(l - 1);
if(pera % 2 == 1) {
lld tmp = neg;
neg = -pos; pos = -tmp;
}
ans = (ans + pos) % mod;
ans = (ans + neg + mod) % mod;
while(ans < 0) {
ans = ans + mod;
}
ans %= mod;
flip = false;
if((oper[r] - oper[l - 1]) % 2) flip = true;
getans();
for(int i = 0; i < cnt; i++) {
cout << ss[i];
}
cout << endl;
}
return 0;
}
Box
分析:
这个题目开始把我搞惨了 最后开了两个dp数组过了
dp[i][0]表示当前位置的1不动
dp[i][1]表示当前位置的1向左
dp[i][2]表示当前位置的1向右
f[i][0]表示当前位置没有1从左右转移过来
f[i][1]表示当前位置的1由左边转移过来
f[i][2]表示当前位置的1由右边转移过来
最后分四种情况就好 避免重复计算 对于当前位置为1的才计算到贡献里
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=1e6+5;
ll a[maxn],b[maxn],n,dp[maxn][3],f[maxn][3];//0表示不动 1表示左 2表示右 0表示没有 1表示由左 2表示由右
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1,x;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++){
if(b[i]==1){
if(b[i-1]==1){
dp[i][0]=max(dp[i-1][0],dp[i-1][1])+a[i];
dp[i][1]=dp[i-1][1]+a[i-1];
dp[i][2]=max(dp[i-1][0],max(dp[i-1][2],dp[i-1][1]))+a[i+1];
}
else{
dp[i][0]=max(f[i-1][0],f[i-1][1])+a[i];
dp[i][1]=f[i-1][2]+a[i-1];
dp[i][2]=max(f[i-1][0],f[i-1][1])+a[i+1];
}
}
else {
if(b[i-1]==1){
f[i][0]=max(dp[i-1][0],dp[i-1][1]);
f[i][1]=dp[i-1][2];
f[i][2]=max(dp[i-1][0],dp[i-1][1]);
}else{
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=0;
f[i][2]=max(f[i-1][0],f[i-1][1]);
}
}
}
if(b[n]==1)cout<<max(dp[n][0],max(dp[n][1],dp[n][2]))<<endl;
else cout<<max(f[n][0],max(f[n][1],f[n][2]))<<endl;
}
还有一个更简单的方法 其实做题的时候是想到了的 只是不会操作
本题目唯一一个麻烦点就在于左右都可以移动 这样我们转移的时候无法保证后效性
但是我们可以让b数组统一往后移动一个单位 这样从以前的 i-1 i i+1 就变成了 i-2 i-1 i了
#include <iostream>
using namespace std;
#define int long long
const int N=1000100;
int n,m,a[N],b[N];
int f[N];
signed main(){
cin>>n;
for(int i=2;i<=n+1;++i) scanf("%lld",&a[i]);
for(int i=3;i<=n+2;++i) scanf("%lld",&b[i]);
for(int i=3;i<=n+2;++i){
if(b[i]){
f[i]=max(f[i],f[i-1]+a[i]);
f[i-1]=max(f[i-1],f[i-2]+a[i-1]);
f[i-2]=max(f[i-2],f[i-3]+a[i-2]);
}
f[i-2]=max(f[i-2],f[i-3]);
f[i-1]=max(f[i-1],f[i-2]);
f[i]=max(f[i],f[i-1]);
}
cout<<f[n+2];
return 0;
}
Link with Centrally Symmetric Strings
分析:
很好想到用马拉车算法 多开一个数组存下每个对称字符的对应回文字符
问题变成了怎么判断是否能够组成长回文串
这个问题也算比较经典的吧!!!!!!!
考虑在马拉车算法里面进行跟新我们的dp数组
while(i-g[i]>=1&&i+g[i]<=2*n&&s[i-g[i]]==w[s[i+g[i]]]) {
if(g[i]!=0)
f[i+g[i]-1]|=f[i-g[i]];
g[i]++;
}
if(g[i]!=0)
f[i+g[i]-1]|=f[i-g[i]];
完整代码
#include <bits/stdc++.h>
using namespace std;
char s[2000010],w[32767];
int n,g[2000010],f[2000010];
vector<int>v;
void sol(){
cin>>(s+1),n=strlen(s+1);
for(int i=n;i>=1;i--) s[2*i-1]=s[i],s[2*i]='.',f[2*i-1]=f[2*i]=0;
s[2*n]='@';f[0]=1;
for(int i=1,p=0,r=0;i<=2*n;i++){
if(r>=i) g[i]=min(g[2*p-i],r-i+1);
else g[i]=(s[i]==w[s[i]]);
while(i-g[i]>=1&&i+g[i]<=2*n&&s[i-g[i]]==w[s[i+g[i]]]) {
if(g[i]!=0)
f[i+g[i]-1]|=f[i-g[i]];
g[i]++;
}
if(g[i]!=0)
f[i+g[i]-1]|=f[i-g[i]];
if(i+g[i]-1>r) r=i+g[i]-1,p=i;
}
if(f[n*2-1]) puts("Yes");
else puts("No");
}
int main(){
w['o']='o',w['s']='s',w['x']='x',w['z']='z',w['b']='q',w['d']='p',w['p']='d',w['q']='b',w['n']='u',w['u']='n',w['.']='.';
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
cin>>T;
while(T--) sol();
}
Ama no Jaku
分析:根据样例可以判断出 最后要满足条件一定是全0或者全1
问题转化为通过最少的步骤来将矩阵排列为全0或全1行
因为每行每列最多操作一次 所以我们先将行都转为与第一行相同的 设这样的操作有h1次
再将列全部转化为1的步骤为l1次
这样答案就变成了去min(min(l1,n-l1)+h1,min(l1,n-l1)+n-h1)
#include<iostream>
using namespace std;
int n;
string s[2020];
int main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>s[i];
int t1=0,t2=0;
for(int i=1;i<=n;++i){
if(s[1]==s[i])continue;
for(int j=0;j<n;++j){
if(s[i][j]=='0')s[i][j]='1';
else s[i][j]='0';
}
if(s[i]==s[1])++t1;
else{
cout<<-1;
return 0;
}
}
for(int i=0;i<n;++i)
if(s[1][i]=='1')++t2;
cout<<min(min(n-t2,t2)+t1,min(n-t2,t2)+n-t1);
}
Insert 1, Insert 2, Insert 3, ...
分析:
感觉很简单的 但是做了很久还是没想出来 最后还是强大的队友想到了
答案就是所有以1为左区间的最大区间长度之和 问题变成了怎样快速找到每个1能扩展的最长长度
我们可以从最右边的1向左边扩展 对于每个1 我们维护一个集合(用map就行)这样两个集合是可以合并的
我们对于每个1 暴力向右扩展 遇到1就合并 直到不能扩展为止 这里用到启发式合并 能保证每个数都只会被加入一次
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=1e6+5;
map<int,int>p[maxn];
map<int,int>::iterator it;
stack<int>st;
ll ans;
int n,cnt;
int a[maxn],be[maxn],ed[maxn];
void merge(int x,int y){
int ii=be[x],jj=be[y];
if(p[ii].size()>p[jj].size())
swap(ii,jj);
else be[x]=be[y];
for(it=p[ii].begin();it!=p[ii].end();it++)
p[jj][it->first]+=it->second;
}
void calc(ll L,ll R){
ans=ans+(R-L+1ll);
return;
}
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]==1){
st.push(i);
be[i]=++cnt;
p[cnt][1]++;
ed[i]=i;
}
}
while(!st.empty()){
int u=st.top();st.pop();
for(int i=u+1;i<=n+1;i++){
if(a[i]!=1){
if(p[be[u]][a[i]-1]>0)
p[be[u]][a[i]]++,p[be[u]][a[i]-1]--;
else {
ed[u]=i-1;
break;
}
}else{
merge(u,i);
ed[u]=ed[i];
i=ed[i];
}
}
calc(u,ed[u]);
}
cout<<ans<<endl;
}
看到有个代码更加简洁的做法
我们找以每个i为右端点 满足的左端点1的个数(因为左端点一定是1)再累加起来就可
对于每个数 如果是1 将它压入栈中 不是1的话 我们记录它的根源1在哪个位置
对于每一个i作为右端点 栈中的1要满足条件 栈中1的位置一定要小于 i的根源1
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
vector<int>s,p[N];
int n,ans;
signed main()
{
cin>>n;
for(int i=1,x,y;i<=n;i++)
{
cin>>x;
x--;
if(!x)p[1].push_back(i),s.push_back(i),ans+=s.size();
else if(p[x].empty())s.clear();
else
{
y=p[x].back(),p[x].pop_back(),p[x+1].push_back(y);
while(!s.empty()&&s.back()>y)s.pop_back();
ans+=s.size();
}
}
cout<<ans<<endl;
return 0;
}
Permutation and Primes
我在牛客博客里面写的很清楚
https://blog.nowcoder.net/n/15c114874e8b4082b123968f0c93fe86
Scheming Furry
分析:
怎么判断能否还原呢? 直接模拟冒泡排序即可
怎么判断谁输谁赢呢 看需要操作的奇偶性即可
分情况讨论就好 就是复杂一点
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,a[N][N];
int T;
int main()
{
cin>>T;
while(T--)
{
int hc=0,lc=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;++i)
{
int p=i;
for(int j=i+1;j<=n;++j)
if(a[j][1]<a[p][1]) p=j;
if(i!=p)hc++,swap(a[i],a[p]);//可以直接交换二维数组的两行
}
for(int i=1;i<=m;++i)
{
int p=i;
for(int j=i+1;j<=m;++j)
if(a[1][j]<a[1][p]) p=j;
if(i!=p){
lc++;
for(int j=1;j<=n;++j) swap(a[j][i],a[j][p]);
}
}
int ans=2;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(a[i][j]!=(i-1)*m+j)
ans=0;
if(ans)
{
if(hc==1&&lc==0)ans=-1;
else if((hc&1)!=(lc&1)){
if(m==2)ans=-1;
else ans=0;
}
else {
if(n==2)ans=1;
else ans=0;
}
}
if(ans==0) puts("NSFW");
else if(ans==-1) puts("FOX");
else puts("CAT");
}
return 0;
}
Non-Puzzle: Error Permutation
分析:
注意n<=5000
想要保证区间不满足的比较简单只需要一个位置不满足就好 但是想要区间满足就不好满足 所以考虑反着来考虑不满足的
主要是重复性问题 怎么解决 n<=5000 这样其实重复的不重要 我们对之进行标记就行 我们只需要找从来都没有被标记的区间就好
具体做法 枚举每个位置i 设他位于区间的第k个位置(只需要把左端点枚举到i的排名就确定了) 然后我们再找满足的右端点
设i的前面有tot个比a[i]小的 这样后面满足的范围 为小于a[i]的有 k-tot-1和k-tot之间
然后我们区间打标记 最后再枚举一遍区间 找没有标记的区间就是一定满足的点
这个题非常巧妙的避开了重复性的问题
还有要记得 多数据初始化的时候 不要用memset 容易超时
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int N=5005;
int vis[N][N],a[N],leq[N][N];
int n;
ll ans = 0;
int main() {
int T; scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i = 1; i <= n; i++)scanf("%d",&a[i]);
a[n+1]=ans=0;
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
leq[i][j] += leq[i][j - 1]+(a[j]<a[i]);
for(int i = 1; i <= n; i++) {
int k = 1, tota = 0;
int pos1 = i, pos2 = i;
for(int l = i; l >= 1; l--) {
if(a[l] < a[i]) tota++;
while(leq[i][pos1] < k - tota - 1 && pos1 <= n) pos1++;
while(leq[i][pos2] < k - tota && pos2 <= n) pos2++;
if(leq[i][pos1] == k - tota - 1 && (leq[i][pos2] == k - tota || pos2 == n + 1) && pos1 <= n)
vis[l][pos1]++,vis[l][pos2]--;
k++;
}
}
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++) {
vis[i][j] += vis[i][j - 1];
if(vis[i][j] == 0)ans++;
}
printf("%lld\n", ans);
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
vis[i][j]=leq[i][j]=0;
}
return 0;
}
小美的树上染色
https://ac.nowcoder.com/acm/contest/63585/D
分析:
读完题就能出思路 就是一个树形模板题目
dp[u][0]表示u为根节点 且u节点不染色的最大值
dp[u][1]表示u为根节点 且u节点与其中一个儿子染色的最大值
dp[u][2]表示u为根节点 且u节点与父亲染色的最大值
dp[u][0]和dp[u][2]的转移都一样
dp[u][0]+=max(dp[to][0],dp[to][1]);
dp[u][2]+=max(dp[to][0],dp[to][1]);
麻烦一点的就是dp[u][1]
其中只有一个儿子的贡献为dp[to][2] 其他所有儿子都为max(dp[to][0],dp[to][1]);
我们现将所有儿子的贡献都变成max(dp[to][0],dp[to][1])
然后我们找儿子中最大的dp[to][2]-max(dp[to][0],dp[to][1])再加上就可以了
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=1e5+5;
ll dp[maxn][3],a[maxn];//0 表示没染色 1 表示与儿子染色 2表示与父亲染色
vector<int>Q[maxn];
int n;
bool pd(ll x,ll y){
ll res=x*y;
ll q=sqrt(res);
return (q*q==res);
}
void dfs(int,int);
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++){
int x,y;
cin>>x>>y;
Q[x].push_back(y);
Q[y].push_back(x);
}
dfs(1,1);
cout<<max(dp[1][0],dp[1][1])<<endl;
}
void dfs(int u,int fa){
ll res=0,id=0;
for(int i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(to==fa)continue;
dfs(to,u);
dp[u][0]+=max(dp[to][0],dp[to][1]);
if(pd(a[u],a[to]))
if(res<dp[to][2]-max(dp[to][0],dp[to][1])+1){
res=dp[to][2]-max(dp[to][0],dp[to][1])+1;
id=to;
}
dp[u][1]+=max(dp[to][0],dp[to][1]);
dp[u][2]+=max(dp[to][0],dp[to][1]);
}
if(id)
dp[u][1]+=res+1;
}
/*
9
2 2 2 2 2 2 2 2 2
1 2
2 3
3 4
4 5
1 6
6 7
6 8
1 9
*/
Grayscale Confusion
题目大意就是 给三维偏序标号排序 然后第一个偏序和第二个偏序一定要相同
分析:
很好想到拓扑排序 但是怎么保证第一个和第二个偏序一定要相等呢
可以将1和2缩成一个点 建边就照常建边就行 然后直接打个拓扑排序就好
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const int maxn=1005;
struct node{
int xx,yy,zz;
}a[maxn];
vector<int>Q[maxn];
int n,du[maxn],rk[maxn],pre[maxn];
bool pd(int ii,int jj){
return (a[ii].xx<a[jj].xx)&&(a[ii].yy<a[jj].yy)&&(a[ii].zz<a[jj].zz);
}
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].xx>>a[i].yy>>a[i].zz;
if(pd(1,2)||pd(2,1)){
cout<<"-1"<<endl;
return;
}
for(int i=3;i<=n;i++){
if(pd(1,i)||pd(2,i))Q[1].push_back(i),du[i]++;
if(pd(i,1)||pd(i,2))Q[i].push_back(1),du[1]++;
}
for(int i=3;i<=n;i++)
for(int j=3;j<=n;j++)
if(pd(i,j))Q[i].push_back(j),du[j]++;
queue<int>q;
for(int i=1;i<=n;i++)
if(!du[i])q.push(i),rk[i]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<Q[u].size();i++){
int to=Q[u][i];
if(du[to]==1)rk[to]=rk[u]+1;
du[to]--;
if(!du[to])q.push(to);
}
}
for(int i=1;i<=n;i++)
if(i==2)cout<<rk[1]<<endl;
else cout<<rk[i]<<endl;
}
IUPC
分析:就是一道非常模板的状压dp 但是好久都没有做过状压dp了 都忘记了
枚举时间点 枚举最后m个的状态 枚举已经选了多少个
这样转移方程也就很好写出来了
如果当前位不选
状态从j 转移到j>>1
dp[i+1][j>>1][z]+=dp[i][j][z];
如果要选当前位
状态从j 转移到j>>1|(1<<(k-1)) 这里注意就是我们存贮的是最后m位的倒序 原因是这样转移方便
方案数呢?
前面已经选了z个 当前位置还有sum[i+1]-z个可以选 (其中sum[]数组存储的是当前位置可以选择的个数)
dp[i+1][(j>>1)|(1<<(k-1))][z+1]+=dp[i][j][z]*(sum[i+1]-z)
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int mod=1e9+7;
const int maxn=305;
void solve();
int n,t,k,m;
ll a[maxn],sum[maxn],dp[maxn][1050][maxn];
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n>>t>>k>>m;
for(int i=1,x;i<=n;i++)cin>>x,a[x]++;
for(int i=1;i<=t;i++)sum[i]=sum[i-1]+a[i];
int SK=1<<k;
dp[0][0][0]=1;
for(int i=0;i<t;i++)
for(int j=0;j<SK;j++){
int num=__builtin_popcount(j);
if(num>m)continue;
for(int z=0;z<=sum[i+1];z++){
dp[i+1][j>>1][z]=(dp[i+1][j>>1][z]+dp[i][j][z])%mod;
if(__builtin_popcount(j>>1)+1<=m){
int nx=(j>>1)|(1<<(k-1));
dp[i+1][nx][z+1]=(dp[i+1][(j>>1)|(1<<(k-1))][z+1]+dp[i][j][z]*(sum[i+1]-z))%mod;
}
}
}
ll ans=0;
for(int i=0;i<SK;i++)
ans=(ans+dp[t][i][n])%mod;
cout<<ans<<endl;
}
Non-Puzzle: Segment Pair
分析:
比赛的时候实在不知道该怎么做
首先我们处理每条线段 将线段[L,R]拆分为左右两个点 L是+ R是-
这样子只要我们排序之后顺序枚举 满足有n个满足即可 细节都在代码里面 也不难想到 就是多记录了一些东西而已
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5, mod = 1e9 + 7;
int n, l1, r1, l2, r2, a[N], ac[3], p[N];
struct nood
{
int x, y, id;
};
vector<nood> ax;
bool cmp(nood a, nood b)
{
if (a.x != b.x)
return a.x < b.x;
return a.y < b.y;
}
int main()
{
cin >> n;
p[0] = 1;
for (int i = 1; i <= n; i++)
p[i] = p[i - 1] * 2ll % mod;
for (int i = 1; i <= n; i++)
{
cin >> l1 >> r1 >> l2 >> r2;
ax.push_back({l1, 1, i});
ax.push_back({r1 + 1, -1, i});
ax.push_back({l2, 1, i});
ax.push_back({r2 + 1, -1, i});
}
sort(ax.begin(), ax.end(), cmp);
ac[0] = n;
long long ans = 0;
for (int i=0; i<ax.size(); i++)
{
nood v=ax[i];
ac[a[v.id]]--;
a[v.id] += v.y;
if (v.y == 1 && !ac[0])
ans += p[ac[2]], ans %= mod;
ac[a[v.id]]++;
}
cout << ans;
}
Non-Puzzle: Game
分析:
再回顾一下线性基的重要性质
1.线性基中的元素相互异或得到的集合与原序列相互异或得到的集合等价。
2.线性基是满足性质1的最小的集合,在log级别
3.线性基没有异或和为0的子集
4.线性基中通过不同操作异或成的值不相同(可以通过3证明)
利用好性质4就可以很好的理解这个题目
#include <bits/stdc++.h>
#define A (30)
int n, k;
std::set<int> s;
int a[A+10];
void solve(){
s.clear(); std::fill(a, a+A+1, 0);
scanf("%d%d", &n, &k);
bool f = 0;
for (int i = 1, x; i <= n; i++){
scanf("%d", &x);
if (!f){
s.insert(k ^ x);
f |= s.count(x);
}
}
if (f) { puts("Alice"); return; }
int cnt = 0;
for (auto x : s)
for (int j = A; ~j; j--)
if (x & 1<<j){
if (!a[j]){
a[j] = x; cnt++; break;
} else x ^= a[j];
}
puts((1<<cnt)==s.size() ? "Bob" : "Draw");
}
int main(){
int T; scanf("%d", &T);
while (T--) solve();
return 0;
}
Beautiful Sequence
分析:
相邻两个只用判断最高位即可满足!!!
这样相邻的都满足了 剩下位置没有确定的就直接什么取值都可以
这个处理K的方式非常特别很牛逼!!!
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
long long n,k,b[N],T,c[N],d[50];
int main(){
cin>>T;
while(T--){
cin>>n>>k;
int f=1;
for(int i=1;i<n;i++)cin>>b[i],c[i]=c[i-1]^b[i];
int t=0;
memset(d,-1,sizeof(d));
for(int i=1;i<n&&f;i++){
for(int j=29;j>=0;j--){
if(b[i]>>j){
if(d[j]!=-1&&d[j]!=1^((c[i]>>j)&1)){
f=0;
break;
}
d[j]=1^((c[i]>>j)&1);
break;
}
}
}
k--;
for(int i=0;i<=29;i++){
if(d[i]==-1){
d[i]=k%2;
k/=2;
}
}
if(k>0||f==0)puts("-1");
else {
int p=0;
for(int i=29;i>=0;i--){
p=p<<1|d[i];
}
for(int i=0;i<n;i++){
printf("%d ",(p^c[i]));
}
puts("");
}
}
}
We Love Strings
分析:
方法一:考虑从前往后分析即可 看当前能够匹配的方案数 直接dfs 很好想到
就是dfs过程是传递vector
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int ans=0,n;
string st[500];
void dfs(int len,vector<int> &vec,int tmp){
if (vec.size()==0) return;
bool flag=0;
vector<int> v,v0,v1;
for (auto id:vec)
if (st[id].size()==len) flag=1;
else v.push_back(id);
if (flag) ans=(ans+tmp)%mod;
for (auto id:v)
if (st[id][len]=='0') v0.push_back(id);
else if (st[id][len]=='1') v1.push_back(id);
else v0.push_back(id),v1.push_back(id);
if (v0.size()==v.size() && v1.size()==v.size()) dfs(len+1,v,tmp*2%mod);
else dfs(len+1,v0,tmp),dfs(len+1,v1,tmp);
}
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
vector<int> vec;
cin>>n;
for (int i=1; i<=n; ++i){
cin>>st[i];
vec.push_back(i);
}
dfs(0,vec,1);
cout<<ans;
return 0;
}
方法二:看数据分析
400的数据就很奇怪 并且要求总的长度也是要小于400
不妨分为长度<=20和长度大于20
对于长度<=20 的串 可以直接暴力用map
对于长度>20的串 我们发现这样的串一定是<20个 所以可以用容斥
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
void solve();
const ll mod=998244353;
const int maxn=405;
ll n,ans,edd,ii,num,maxx,pd[(1<<20)+5];
vector<string>q,Q;
string s[maxn];
map<string,int>mp;
void dfs(string ss,int now){
if(now==edd){
mp[ss]=1;
return;
}
if(q[ii][now]=='?')
dfs(ss+'1',now+1),dfs(ss+'0',now+1);
else if(q[ii][now]=='1')
dfs(ss+'1',now+1);
else dfs(ss+'0',now+1);
}
ll ck(int x){
vector<int>tt;
int sz=0;
ll re=1;
for(int i=0;i<num;i++)
if((x>>i)&1){
tt.push_back(i);
if(sz==0)sz=Q[i].size();
else if(sz!=Q[i].size())return 0;
}
for(int i=0;i<sz;i++){
char pp='!';
for(int j=0;j<tt.size();j++){
if(Q[tt[j]][i]=='?')continue;
if(pp=='!')pp=Q[tt[j]][i];
else if(pp!=Q[tt[j]][i])return 0;
}
if(pp=='!')re=(re*2ll)%mod;
}
return re;
}
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>s[i];
if(s[i].size()<20)q.push_back(s[i]);
else Q.push_back(s[i]);
}
for(int i=0;i<q.size();i++)ii=i,edd=q[i].size(),dfs("",0);
ans=mp.size();
num=Q.size();
maxx=1ll<<num;
for(int i=1;i<maxx;i++)pd[i]=ck(i);
for(int i=1;i<maxx;i++){
if(!pd[i])continue;
int p=__builtin_popcount(i);
if(p&1)ans=(ans+pd[i])%mod;
else ans=(ans-pd[i]+mod)%mod;
}
cout<<ans<<endl;
}
Cyperation
分析:
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e6+5;
ll xx[maxn],t[maxn],x[maxn];
ll sum,ou,cnt,ans;
bool vis[maxn];
void solve();
void calc1();
void calc2();
int n,k;
int main(){
int T;cin>>T;
while(T--)solve();
return 0;
}
void solve(){
cin>>n>>k;
bool pd=1;ans=1;
for(int i=1;i<=n;i++){
cin>>xx[i];
vis[i]=0;
if(xx[i]!=0)pd=0;
}
if(k>(n/2)){
if(pd==1)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
return;
}
for(int i=1;i<=n;i++){
if(vis[i])continue;
cnt=0;
for(int j=i;;j+=k){
if(j>n)j-=n;
if(vis[j]==1)break;
else x[++cnt]=xx[j],vis[j]=1;
}
if(cnt&1)calc1();
else calc2();
if(ans==0){
cout<<"NO"<<endl;
return;
}
}
cout<<"YES"<<endl;
return;
}
void calc1(){
sum=ou=0;
for(int i=1;i<=cnt;i++){
sum+=x[i];
if((i&1)==0)ou+=x[i];
}
if(sum&1){ans=0;return;}
t[cnt]=sum/2-ou;
for(int i=cnt;i>=2;i--)
t[i-1]=x[i]-t[i];
for(int i=1;i<=cnt;i++)
if(t[i]<0){ans=0;return;}
}
void calc2(){
ll res=0,minn=1e18,maxx=0;
for(int i=1;i<=cnt;i++){
if(i&1)res+=x[i];
else res-=x[i];
if(i&1)
minn=min(minn,res);
else maxx=max(maxx,res);
}
if(maxx>minn||res!=0)ans=0;
return;
}
Tree
分析:
很明显的从小边开始处理 还是之前一道题目的复杂度 子树选点n方的复杂度是可以的
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int fa[3001],sz[3001],n,a[3001],co[3001];
vector<ll>dp[3001];
struct edge
{
int u,v;
long long w;
}e[3001];
bool cmp(edge i,edge j)
{
return i.w<j.w;
}
int find(int i)
{
return fa[i]==i?i:fa[i]=find(fa[i]);
}
signed main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),fa[i]=i,sz[i]=1,dp[i].resize(2);
for(int i=1;i<=n;i++)scanf("%d",&co[i]),dp[i][a[i]^1]=-co[i],dp[i][a[i]]=0;
for(int i=1;i<n;i++)scanf("%d%d%lld",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+n,cmp);
for(int i=1;i<n;i++)
{
int u=find(e[i].u),v=find(e[i].v);
vector<ll>tmp(sz[u]+sz[v]+1,-0x3f3f3f3f3f3f3f3f);
for(int j=0;j<=sz[u];j++)
for(int k=0;k<=sz[v];k++)
tmp[j+k]=max(tmp[j+k],dp[u][j]+dp[v][k]+((ll)(j*(sz[v]-k))+((sz[u]-j)*k))*e[i].w);
dp[u].swap(tmp);
fa[v]=u,sz[u]+=sz[v];
}
ll ans=0;
for(ll v:dp[find(1)])ans=max(ans,v);
printf("%lld",ans);
return 0;
}
Distance
分析:
上面组合数转化利用到 范德蒙德卷积
https://www.cnblogs.com/wzxbeliever/p/17700264.html
idol!!
分析:
有可能会爆ll 所以用int128
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
ll nq5[100];
ll ans = 0;
ll cacu(ll y, ll t)
{
ll lun = (t+2) / (y*2);
ll yu = (t+2) % (y*2)/2;
//a+bi
ll a = y/2;
ll b = y*2;
ll tans = 0;
if(lun >0)
tans += (a + a + b* (lun-1))*lun/2;
tans += yu*lun*2;
if(yu > y/2 +1)
tans += yu - (y/2+1);
return tans;
}
inline void print(__int128 x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9)
print(x / 10);
putchar(x % 10 + '0');
}
int main(){
nq5[1] = 5;
for(int i=2; i<100; i++){
nq5[i] = nq5[i-1]*5;
if(nq5[i] > 1e18)break;
}
long long tt; cin>>tt;
ll t = ll(tt);
bool b = 0;
if(t%2 == 1){
b=1; t--;
}
for(int i=1; nq5[i]<=t; i++){
ans += cacu(nq5[i], t);
}
// cout<<ans<<endl;
if(b){
t++;
for(int i=1; nq5[i]<=t; i++){
ll ss = t / nq5[i];
ans += (ss+1)/2;
}
}
print(ans);
}
The Yakumo Family
分析:
这样的题目肯定会拆位 对每一位进行处理
很好想到用pre[i]表示前i个位置一个区间所有情况的Σ ,suf[i]表示后i个位置一个区间所有情况的Σ
这样枚举中间区间[L,R]的情况×pre[L-1]×suf[R+1]即可
考虑枚举左端点L,因为我们是每一位每一位处理的,所以对于中间区间有贡献的一定是左右端点之间的1为奇数的
这样其实我们只用统计出对于每个左端点,满足左右端点之间1的个数为奇数的右端点R ,suf[R]的总和
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=2e5+5;
const int mod=998244353;
ll n,ans;
ll a[maxn],aa[maxn],pre[maxn],suf[maxn],sum[maxn];
void solve();
int main(){
int T;T=1;
while(T--)solve();
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],aa[i]=aa[i-1]^a[i];
for(int p=0;p<=30;p++){
ll tot1=0,tot0=0,tot;
for(int i=1;i<=n;i++){
int u=(aa[i]>>p)&1;
if(u)tot=tot0+1,tot1++;
else tot=tot1,tot0++;
pre[i]=(pre[i]+tot*(1ll<<p)%mod)%mod;
}
}
for(int i=1;i<=n;i++)pre[i]=(pre[i-1]+pre[i])%mod;
aa[n+1]=0;
for(int i=n;i>=1;i--)aa[i]=aa[i+1]^a[i];
for(int p=0;p<=30;p++){
ll tot1=0,tot0=0,tot;
for(int i=n;i>=1;i--){
int u=(aa[i]>>p)&1;
if(u)tot=tot0+1,tot1++;
else tot=tot1,tot0++;
suf[i]=(suf[i]+tot*(1ll<<p)%mod)%mod;
}
}
for(int i=n;i>=1;i--)suf[i]=(suf[i+1]+suf[i])%mod;
for(int p=0;p<=30;p++){
ll tot1=0,tot0=0,tot;
for(int i=n;i>=1;i--){
int u=(aa[i]>>p)&1;
if(u)tot=tot0,tot1=(tot1+suf[i])%mod;
else tot=tot1,tot0=(tot0+suf[i])%mod;
sum[i]=(sum[i]+tot*(1ll<<p)%mod)%mod;
}
}
for(int i=2;i<=n;i++)
ans=(ans+pre[i-1]*sum[i]%mod)%mod;
cout<<ans<<endl;
}
根据拆位性质 这个题目还有另一种做法 甚至可以继续拓展不止三个区间
还是每一位每一位的处理
依旧pre[i]表示前i个位置1个区间所有情况的总和
如果我们再进行一次一样的操作 就会发现pre[i]变成了前i个位置2个区间的所有情况的总和
这个原理和之前统计所有suf[R]的和是一样的
如果再进行一次,就表示3个区间所有的情况了,就是答案
妙啊妙啊!
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
ll a[200005],b[200005],nb[200005];
int main()
{
int n,i;cin>>n;b[0]=1;
for (i=1;i<=n;i++) {cin>>a[i];a[i]^=a[i-1];b[i]=1;}
for (int o=0;o<3;o++)
{
for (int j=31;j>=0;j--)
{
ll p[2]={b[0],0};
for (i=1;i<=n;i++)
{
nb[i]+=(1<<j)*p[((a[i]>>j)&1)^1]%mod;
(p[(a[i]>>j)&1]+=b[i])%=mod;
}
}
b[0]=0;
for (i=1;i<=n;i++) b[i]=(b[i-1]+nb[i])%mod,nb[i]=0;
}
cout<<b[n];
}
Red and Blue and Green
分析:
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
int inv[1003],p[1003],ans[1003],nex[1003];//inv表示以当前位置开始的奇偶性变换的次数,p为排列顺序,ans为答案,nex为当前位置对应的区间
struct node
{
int l,r,w;
}s[1003];
bool cmp(node a,node b)
{
return a.r-a.l<b.r-b.l;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>s[i].l>>s[i].r>>s[i].w;
for(int i=1;i<=n;i++)
p[i]=i,nex[i]=i;
sort(s+1,s+m+1,cmp);
for(int i=1;i<=m;i++)
{
int l=s[i].l,r=s[i].r,w=s[i].w,num=0;
if(l==r&&w==1)
{
cout<<"-1"<<endl;
return 0;
}
for(int j=l;j<=r;j++)
num+=inv[j];
if(num%2!=w)
{
swap(p[nex[l]],p[nex[l]+1]);
inv[l]++;
}
nex[l]=r;
}
for(int i=1;i<=n;i++)
ans[p[i]]=i;
for(int i=1;i<=n;i++)
cout<<ans[i]<<" ";
return 0;
}
Jujubesister
分析:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int a[N],sum[N],cnt[N],col[N],n,m;
long long tot[N],ans[N],res;
struct query{
int l,r,id,bid;
bool operator <(const query &x)const{
return bid==x.bid?(bid&1?r<x.r:r>x.r):bid<x.bid;
}
}q[N];
const int BLEN=1500;
inline int lowbit(int x){
return x&-x;
}
void add(int x){
while(x<=n){
a[x]++;
x+=lowbit(x);
}
}
int get_sum(int x){
int ans=0;
while(x>0){
ans+=a[x];
x-=lowbit(x);
}
return ans;
}
void add(int p,int type){
int c=col[p];
res+=(tot[c]-1ll*cnt[c]*sum[p])*type;
++cnt[c];
tot[c]+=sum[p];
}
void del(int p,int type){
int c=col[p];
--cnt[c];
tot[c]-=sum[p];
res-=(tot[c]-1ll*cnt[c]*sum[p])*type;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>col[i],
sum[i]=get_sum(col[i]-1),
add(col[i]);
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r,
q[i].id=i,q[i].bid=q[i].l/BLEN;
sort(q+1,q+m+1);
int l=1,r=0;
for(int i=1;i<=m;++i){
while(l>q[i].l)add(--l,1);
while(r<q[i].r)add(++r,-1);
while(l<q[i].l)del(l++,1);
while(r>q[i].r)del(r--,-1);
ans[q[i].id]=res;
}
for(int i=1;i<=m;++i)
cout<<ans[i]<<endl;
}
Circle of Mistery
分析:
#include "bits/stdc++.h"
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int ans = 1E9;
for (int l = 0; l < n; l++) {
if (a[l] >= k) {
ans = 0;
continue;
}
i64 sum = 0;
int x = 0;
priority_queue<int> q;
for (int r = l; r < n; r++) {
if (a[r] >= 0) {
sum += a[r];
x++;
} else {
q.push(a[r]);
}
while (!q.empty()) {
auto cur = q.top();
if (sum + cur >= k) {
sum += cur;
q.pop();
x++;
} else {
break;
}
}
if (sum >= k && x != 0) {
ans = min(ans, r - l + r - l + 1 - x);
}
}
}
cout << (ans == 1E9 ? -1 : ans) << '\n';
return 0;
}
Qu'est-ce Que C'est?
分析:
#include<bits/stdc++.h>
using namespace std;
const int N=5005,mod=998244353;
int n,m;
int dp[N][N*2],sum[N][N*2];
int main(){
cin>>n>>m;
for(int i=m;i>=-m;i--) dp[1][i+m]=1,sum[1][i+m]=(sum[1][i+m+1]+dp[1][i+m]);
for(int i=2;i<=n;i++){
for(int j=m;j>=-m;j--){
if(j>=0) dp[i][j+m]=sum[i-1][j];
else dp[i][j+m]=sum[i-1][m-j];
sum[i][j+m]=(sum[i][j+m+1]+dp[i][j+m])%mod;
}
}
cout<<sum[n][0];
return 0;
}
Auspiciousness
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 605;
int T, n, n2, m;
int fac[maxn];
int c[maxn][maxn];
int f[maxn][maxn][2];
void init ()
{
fac[0] = c[0][0] = 1;
for (int i = 1; i <= n2; ++i) fac[i] = 1ll * fac[i - 1] * i % m;
for (int i = 1; i <= n2; ++i) {
c[i][0] = 1;
for (int j = 1; j <= i; ++j)
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % m;
}
for (int i = 1; i <= n2; ++i)
for (int j = 0; j <= n2; ++j) f[i][j][0] = f[i][j][1] = 0;
}
int main ()
{
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
n2 = n << 1;
init();
f[0][0][0] = f[0][0][1] = 1;
int ans = 0;
for (int i = 1; i <= n2; ++i) {
for (int j = min(i, n); j >= 0; --j) {
for (int k = i - 1; k >= 0; --k) {
int t0 = j - (i - k);
if (t0 < 0 || t0 > min(k, n)) continue;
f[i][j][0] += 1ll * f[k][t0][1] * c[n - t0][i - k] % m;
f[i][j][0] %= m;
ans += 1ll * f[k][t0][1] * c[n - t0][i - k] % m * (i - k - 1) % m * i % m * fac[n2 - i] % m;
ans %= m;
}
for (int k = i - 1; k >= 0; --k) {
int t1 = (i - j) - (i - k);
if (t1 < 0 || t1 > min(n, k)) continue;
f[i][j][1] += 1ll * f[k][j][0] * c[n - t1][i - k] % m;
f[i][j][1] %= m;
ans += 1ll * f[k][j][0] * c[n - t1][i - k] % m * (i - k - 1) % m * i % m * fac[n2 - i] % m;
ans %= m;
}
}
}
ans += (1ll * f[n2][n][0] * n2 % m + 1ll * f[n2][n][1] * n2 % m) % m;
ans %= m;
printf("%d\n", ans);
}
return 0;
}
小雨坐地铁
分析:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<functional>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
struct edge { int to, cost; };
const int max_n = 7e5;
const ll inf = 2e18;
vector<edge> G[max_n];
ll d[max_n];
int n, m;
void dijstra(int s) {
fill(d + 1, d + 1 + n, inf);
priority_queue<pll,vector<pll>,greater<pll>> que;
d[s] = 0;que.push({ d[s],s });
while (!que.empty()) {
pll p = que.top();que.pop();
if (p.first > d[p.second])continue;
d[p.second] = p.first;
for (int i = 0;i < G[p.second].size();i++) {
edge e = G[p.second][i];
if (d[e.to] > d[p.second] + e.cost) {
d[e.to] = d[p.second] + e.cost;
que.push({ d[e.to],e.to });
}
}
}
}
int main() {
ios::sync_with_stdio(0);
int s, t;
cin >> n >> m >> s >> t;
for (int i = 1;i <= m;i++) {
int a, b, c;cin >> a >> b >> c;
int st = n + 1;
for (int j = 1;j <= c;j++) {
int node;cin >> node;n++;
G[n].push_back({ node,0 });
G[node].push_back({ n,a });
}
for (int j = st;j < n;j++) {
G[j].push_back({ j + 1,b });
G[j + 1].push_back({ j,b });
}
}
dijstra(s);
if (d[t] == inf)cout << -1 << endl;
else cout << d[t] << endl;
}
华华开始学信息学
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int max_n = 1e5 + 100;
const int max_sq = 1e3;
int n, m;
ll BIT[max_n];
ll sq[max_sq];
void renew(int id,int val) {
for (;id <= n;id += (id & -id))BIT[id] += val;
}
ll quiry(int id) {
if (id == 0)return 0;
ll res = 0;
for (;id;id -= (id & -id))res += BIT[id];
return res;
}
int main() {
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 1;i <= m;i++) {
int type;int x, y;
cin >> type >> x >> y;
if (type == 1) {
if (x <= sqrt(n))sq[x] += y;
else {
for (int i = 1;i * x <= n;i++)
renew(i * x, y);
}
}
else {
ll ans = quiry(y) - quiry(x - 1);
for (int i = 1;i <= sqrt(n);i++)
ans += (y / i - (x - 1) / i) * sq[i];
cout << ans << endl;
}
}
}