Hint:可以点击右下角的目录符号快速跳转到指定位置
上接:SSF信息社团1月训练题目整理
下接:SSF信息社团寒假训练题目整理(二)
1.25(zzt)
717E
考虑没有颜色限制应该怎么输出路径。容易得到这样一份伪代码:
dfs(x):
print x
for each y in son(x):
dfs(y)
print x
实际上这道题也可以这样走,当递归完儿子之后如果发现自己的颜色是粉色,就按照 (x o ext{father}(x) o x) 再走一遍将 (x) 的颜色变为黑色。若走完路径后发现根节点 (1) 是粉色,令 (y) 为任意一个 (1) 的子节点,按照 (1 o y o 1 o y) 的路径走一遍即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=2e5;
int head[N+10],ver[N*2+10],nxt[N*2+10],tot=0;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
vector<int> ans;
int p[N+10];
void change(int x,int fa)
{
p[x]=-p[x];
p[fa]=-p[fa];
ans.push_back(x);
ans.push_back(fa);
}
void dfs(int x,int fa)
{
// ans.push_back(x);
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(y==fa) continue;
ans.push_back(y);
p[y]=-p[y];
dfs(y,x);
ans.push_back(x);
p[x]=-p[x];
if(p[y]==-1) change(y,x);
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,-1);
printf("1 ");
for(int i=0;(unsigned)i<ans.size();i++) printf("%d ",ans[i]);
if(p[1]==-1)
printf("%d 1 %d",ver[head[1]],ver[head[1]]);
return 0;
}
757C
考虑一组宝可梦按照怎么样的方案进化才能保持前后数量不变。显然,若两个种类 (x) 和 (y) 按照 (x o y)、(y o x) 满足要求,那么在每个人的可宝梦中,这两种可宝梦的数量一定相等。令 (c_{i,j}) 表示第 (i) 个人手中有 (c_{i,j}) 个种类 (j) 的可宝梦,构造哈希函数 (h(j)=sumlimits_{i=1}^n b^{n-i}c_{i,j})((b) 为选择的基数),若 (h(a_1)=h(a_2)=cdots =h(a_p)),那么这 (p) 种精灵能对答案产生 (p!) 的贡献,将所有贡献相乘即可。但显然这个哈希函数存不下,于是把他放到 unsigned long long
里让它自然溢出。只要 (b) 选得足够好,哈希冲撞的概率就足够小,可以通过本题。如果暴力计算 (h(j)),还是会 TLE。发现对于第 (i) 个人,其 (g_i) 只宝可梦的哈希值实际上都是一开始 ( imes b),然后一些宝可梦的哈希值不断加一,于是可以用线段树维护 (h (j))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline void read(int &x)
{
x=0; int f=1;
char c=getchar();
while(c<'0' || c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0' && c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
x*=f;
}
typedef unsigned long long ull;
const int N=1e6,M=1e6,MOD=1e9+7;
const ull base=1e5+3;
struct segment
{
int l,r;
ull mul,hash;
}t[M*4+10];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
t[p].mul=1;
if(l==r)return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void spread(int p)
{
if(t[p].mul!=1ull)
{
if(t[p*2].l!=t[p*2].r) t[p*2].mul*=t[p].mul;
if(t[p*2+1].l!=t[p*2+1].r) t[p*2+1].mul*=t[p].mul;
if(t[p*2].l==t[p*2].r) t[p*2].hash*=t[p].mul;
if(t[p*2+1].l==t[p*2+1].r) t[p*2+1].hash*=t[p].mul;
t[p].mul=1;
}
}
void add(int p,int l,int d)
{
if(t[p].l==t[p].r)
{
t[p].hash+=d;
return;
}
spread(p);
int mid=(t[p].l+t[p].r)/2;
if(l<=mid) add(p*2,l,d);
else add(p*2+1,l,d);
}
void modify()
{
t[1].mul*=base;
spread(1);
return;
}
ull query(int p,int l)
{
if(t[p].l==t[p].r) return t[p].hash;
spread(p);
int mid=(t[p].l+t[p].r)/2;
if(l<=mid) return query(p*2,l);
else return query(p*2+1,l);
}
ull h[N+10];
ull jc[M+10];
int main()
{
int m,n;
read(n);read(m);
build(1,1,m);
for(int i=1;i<=n;i++)
{
int g;
modify();
scanf("%d",&g);
for(int j=1;j<=g;j++)
{
int x;
scanf("%d",&x);
add(1,x,1);
}
}
for(int i=1;i<=m;i++) h[i]=query(1,i);
sort(h+1,h+m+1);
// for(int i=1;i<=m;i++) printf("%llu ",h[i]);
jc[0]=1ull;
for(int i=1;i<=m;i++)jc[i]=jc[i-1]*i%MOD;
ull ans=1;
for(int i=1;i<=m;i++)
{
if(h[i]!=h[i-1] || i==1)
{
int cnt=upper_bound(h+1,h+m+1,h[i])-h;
cnt-=i;
ans=ans*jc[cnt]%MOD;
// printf("cnt:%d ",cnt);
}
}
printf("%llu",ans);
return 0;
}
780D
考虑一种贪心:将每个 (s_1) 的数量计算出来,记为 (c_{s_1})。对于 (c_{s_1}=1) 的 (s_1),让这些串选择 (s_1);否则,让这些串选择 (s_2),同时将这些串的 (s_2) 改为 (s_1)。此流程执行 (n) 次,若 (n) 次后还是不符合条件,则无解,否则输出所有串的 (s_1) 即可。容易证明这是正确的。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<unordered_map>
using namespace std;
const int N=1000;
struct team
{
string s1,s2;
team(){}
team(string ss1,string ss2){s1=ss1;s2=ss2;}
}a[N+10];
bool f1[N+10];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
string s1,s2;
cin>>s1>>s2;
a[i].s1=s1.substr(0,3);
a[i].s2=s1.substr(0,2)+s2.substr(0,1);
// cnt[a[i].s1]++;
}
unordered_map<string,int> cnt;
for(int i=1;i<=n;i++)//别找了这段就是看的题解
{
for(int j=1;j<=n;j++) cnt[a[j].s1]++;
for(int j=1;j<=n;j++)
if(cnt[a[j].s1]>1)
a[j].s1=a[j].s2;
cnt.clear();
}
unordered_map<string,bool> vis;
for(int i=1;i<=n;i++)
{
if(vis[a[i].s1])
{
cout<<"NO";
return 0;
}
else vis[a[i].s1]=1;
}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++) cout<<a[i].s1<<endl;
return 0;
}
1.25(hxy)
817D
这个 trick 有点像 1402A。对于每个 (a_i),使用单调栈求出满足 (maxlimits_{j=maxl_i}^{i-1}{a_j}le a_i) 的最小的 (maxl_i)(若没有,记为 (i),下同),满足 (minlimits_{j=minl_i}^{i-1}{a_j}ge a_i) 的最小的 (minl_i),满足 (maxlimits_{j=i+1}^{maxr_i}{a_j}< a_i) 的最大的 (maxr_i),满足 (minlimits_{j=i+1}^{minr_i}{a_j}>a_i) 的最大的 (minr_i)。此时,(a_i) 对于答案的贡献就是 ((maxr_i-i+1)(i-maxl_i+1)a_i-(minr_i-i+1)(i-minl_i+1)a_i),将贡献累加即可。值得一提的是,为什么我们求左端点的时候是 (le) 和 (ge),求右端点的时候是 (<) 和 (>),这是因为如果都带等于号,会重复统计;如果都不带等于号,会少统计。所以说这两个端点左闭右开,这样就能做到不重不漏。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define int long long
const int N=1e6;
int st[N+10],top=0,a[N+10],n;
int minl[N+10],minr[N+10],maxl[N+10],maxr[N+10];
void init()
{
st[top=1]=1;
for(int i=2;i<=n;i++)
{
while(top && a[st[top]]>a[i]) minr[st[top--]]=i-1;
st[++top]=i;
}
while(top) minr[st[top--]]=n;
st[top=1]=1;
for(int i=2;i<=n;i++)
{
while(top && a[st[top]]<a[i]) maxr[st[top--]]=i-1;
st[++top]=i;
}
while(top) maxr[st[top--]]=n;
st[top=1]=n;
for(int i=n-1;i;i--)
{
while(top && a[st[top]]>=a[i]) minl[st[top--]]=i+1;
st[++top]=i;
}
while(top) minl[st[top--]]=1;
st[top=1]=n;
for(int i=n-1;i;i--)
{
while(top && a[st[top]]<=a[i]) maxl[st[top--]]=i+1;
st[++top]=i;
}
while(top) maxl[st[top--]]=1;
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
init();
// for(int i=1;i<=n;i++)
// {
// printf("min:[%lld, %lld], max:[%lld, %lld]
",minl[i],minr[i],maxl[i],maxr[i]);
// }
int ans=0;
for(int i=1;i<=n;i++)
{
ans+=(i-maxl[i]+1)*(maxr[i]-i+1)*a[i];
ans-=(i-minl[i]+1)*(minr[i]-i+1)*a[i];
}
printf("%lld",ans);
return 0;
}
818E
枚举起点 (l),二分满足 ((prodlimits_{i=l}^r a_i)mod k ot=0) 的最大的 (r),此时对于任意的 (tin[r+1,n]),区间 ([l,t]) 都符合条件,累加即可。可是 (prodlimits_{i=l}^r a_i) 无法使用前缀乘积计算,因为有可能前缀乘积没有关于 (k) 的逆元。于是牺牲 (mathcal O(log n)) 的时间使用线段树维护乘积,建树和查询过程中边计算乘积边取模即可。总的时间复杂度为 (mathcal O(n log^2 n))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=1e5;
int a[N+10],sum[N+10]={1},n,k;
struct seg
{
int l,r;
int sum;
seg(){}
}t[N*4+10];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
if(l==r)
{
scanf("%lld",&t[p].sum);
t[p].sum%=k;
return;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].sum=t[p*2].sum*t[p*2+1].sum%k;
}
int query(int p,int l,int r)
{
if(l<=t[p].l && t[p].r<=r) return t[p].sum;
int mid=(t[p].l+t[p].r)/2;
int ans=1;
if(l<=mid) ans=(ans*query(p*2,l,r))%k;
if(r>mid) ans=(ans*query(p*2+1,l,r))%k;
return ans;
}
signed main()
{
scanf("%lld%lld",&n,&k);
build(1,1,n);
// printf("%lld",query(1,2,2));
int ans=(n+1)*n/2;
for(int i=1;i<=n;i++)
{
int l=i,r=n,p=-1;
while(l<=r)
{
int mid=(l+r)/2;
if(query(1,i,mid)%k==0)
r=mid-1;
else
{
p=mid;
l=mid+1;
}
}
if(~p)ans-=p-i+1;
// printf("%lld ",p);
}
printf("%lld",ans);
return 0;
}
1.25(yh)
有手就行。
808D
比较有趣的一道题。枚举两个区间的分界点,令左边区间的和为 (a),右边区间和为 (b),如果 (| a-b |equiv 1pmod 2),显然这个分界点不可行。否则,若 (a=b),直接输出 YES
即可;若 (a>b) 且左边区间存在一数 (a_i) 使得 (a_i=(a-b)/2),说明此分界点可行,直接输出 YES
;类似地,若 (b>a) 且右边区间存在一数 (a_j) 使得 (a_j=(b-a)/2),说明此分界点可行,输出 YES
。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
typedef long long ll;
const int N=1e5;
int a[N+10];ll sum[N+10];
map<ll,bool> vis;
map<ll,int> Max,Min;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
vis[a[i]]=1;
if(Max[a[i]])Max[a[i]]=max(Max[a[i]],i);
else Max[a[i]]=i;
if(Min[a[i]])Min[a[i]]=min(Min[a[i]],i);
else Min[a[i]]=i;
}
vis[0]=1;
for(int i=0;i<=n;i++)
{
ll s=sum[i]-(sum[n]-sum[i]);
// printf("s:%lld
",s);
if(abs(s)%2) continue;
else if(vis[abs(s)/2])
{
if(s>0)
{
if(Min[abs(s)/2]<=i)
{
printf("YES");
return 0;
}
}
else if(s==0)
{
printf("YES");
return 0;
}
else
{
// printf("Max:%d
",Max[abs(s)/2]);
if(Max[abs(s)/2]>i)
{
printf("YES");
return 0;
}
}
}
}
printf("NO");
return 0;
}
803D
二分即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
using namespace std;
const int N=1e6;
int p[N+10],c[N+10];
int main()
{
int n;
cin>>n;cin.get();
string s;
getline(cin,s);
int len=s.length();
int m=0;
for(int i=0;i<len;i++) if(s[i]==' '||s[i]=='-') p[++m]=i;
p[++m]=len;
int l=0,r=len,ans=0;
p[0]=-1;
for(int i=1;i<=m;i++)
{
if(i!=m) c[i]=p[i]-p[i-1];
else c[i]=p[i]-p[i-1]-1;
// printf("%d ",c[i]);
l=max(l,c[i]);
}
while(l<=r)
{
int mid=(l+r)/2;
int cnt=1,sum=0;
for(int i=1;i<=m;i++)
{
sum+=c[i];
if(sum>mid)
{
sum=c[i];
cnt++;
}
}
if(cnt<=n)
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d",ans);
return 0;
}
//he-ll-o
//0123456
808C
注意到答案一定是 (n) 的因数,所以 (mathcal O(sqrt{n})) 枚举答案 (g),然后判断是否能构造出 (gcd) 为 (g) 的项数为 (k) 的单调递增序列,即判断表达式 (n/gle k(k+1)/2) 成立,在所有成立的 (g) 中取最大的即为答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
const int N=2e5;
typedef long long ll;
int main()
{
ll n,k;
scanf("%lld%lld",&n,&k);
// if(k==1)
// {
// printf("%lld",n);
// return 0;
// }
// printf("k*(k+1)/2:%lld
",k*(k+1)/2);
if(k>=(ll)1e6) printf("-1");
else
{
ll maxi=0;
for(ll i=1;i*i<=n;i++)
{
if(n%i) continue;
if(n/i>=k*(k+1)/2) maxi=max(maxi,i);
if(i*i!=n)
{
// printf("n/(n/i):%lld
",n/(n/i));
// printf("n/i:%lld
",n/i);
if(n/(n/i)>=k*(k+1)/2)
{
maxi=n/i;
break;
}
}
}
if(maxi==0) printf("-1");
else
{
n/=maxi;
for(int i=1;i<k;i++)
{
printf("%lld ",(ll)i*maxi);
n-=i;
}
printf("%lld",n*maxi);
}
}
return 0;
}
Day 2(1.26)
1067A
令 (f(i,j,0/1/2)) 表示第 (i) 个数值为 (j),(a_i) 与 (a_{i-1}) 的关系为 (0/1/2) 时的方案数,其中 (0) 表示 (a_i=a_{i-1}),(1) 表示 (a_i>a_{i-1}),(2) 表示 (a_i<a_{i-1}),可以推出:
两个 (sum) 可以使用前缀和优化成 (mathcal O(1)),总的 dp 复杂度为 (mathcal O(dn)),其中 (d) 为 (a_i) 值域,此题中为 (200)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=1e5,MOD=998244353;
int dp[N+10][201][3],a[N+10],sum[201][3];
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
if(a[1]==-1)
{
for(int i=1;i<=200;i++)
{
sum[i][1]=sum[i-1][1]+1;
sum[i][1]%=MOD;
dp[1][i][1]=1;
}
}
else
{
dp[1][a[1]][1]=1;
for(int i=1;i<=200;i++)
{
sum[i][1]=sum[i-1][1]+dp[1][i][1];
sum[i][1]%=MOD;
}
}
for(int i=2;i<=n;i++)
{
if(a[i]==-1)
{
for(int j=1;j<=200;j++)
{
dp[i][j][0]+=dp[i-1][j][1]+dp[i-1][j][2]+dp[i-1][j][0];
dp[i][j][0]%=MOD;
// for(int k=1;k<j;k++)
// {
// dp[i][j][1]+=dp[i-1][k][0]+dp[i-1][k][1]+dp[i-1][k][2];
// dp[i][j][1]%=MOD;
// }
dp[i][j][1]+=sum[j-1][0]+sum[j-1][1]+sum[j-1][2];
dp[i][j][1]%=MOD;
// for(int k=j+1;k<=200;k++)
// {
// dp[i][j][2]+=dp[i-1][k][2]+dp[i-1][k][0];
// dp[i][j][2]%=MOD;
// }
dp[i][j][2]+=sum[200][2]-sum[j][2]+sum[200][0]-sum[j][0];
dp[i][j][2]%=MOD;
}
}
else
{
dp[i][a[i]][0]+=(dp[i-1][a[i]][1]+dp[i-1][a[i]][2])%MOD+dp[i-1][a[i]][0];
dp[i][a[i]][0]%=MOD;
for(int k=1;k<a[i];k++)
{
dp[i][a[i]][1]+=dp[i-1][k][0]+dp[i-1][k][1]+dp[i-1][k][2];
dp[i][a[i]][1]%=MOD;
}
for(int k=a[i]+1;k<=200;k++)
{
dp[i][a[i]][2]+=dp[i-1][k][2]+dp[i-1][k][0];
dp[i][a[i]][2]%=MOD;
}
}
memset(sum,0,sizeof(sum));
for(int j=1;j<=200;j++)
{
sum[j][1]=sum[j-1][1]+dp[i][j][1];
sum[j][2]=sum[j-1][2]+dp[i][j][2];
sum[j][0]=sum[j-1][0]+dp[i][j][0];
sum[j][1]%=MOD;
sum[j][2]%=MOD;
sum[j][0]%=MOD;
}
}
int ans=0;
if(a[n]==-1)
{
for(int i=1;i<=200;i++)
{
ans+=dp[n][i][0]+dp[n][i][2];
ans%=MOD;;
}
}
else
{
ans=dp[n][a[n]][0]+dp[n][a[n]][2];
ans%=MOD;
}
printf("%lld",(ans+MOD)%MOD);
}
1060D
将左手右手分别排序,计算 (n+sumlimits_{i=1}^nmax{l_i,r_i}) 即为答案。对于所有的 ((l_i,r_i)),一定能构造出一组方案使得在排序前这两组二者之间配对。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5;
typedef long long ll;
int l[N+10],r[N+10];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d %d",&l[i],&r[i]);
sort(l+1,l+n+1);
sort(r+1,r+n+1);
ll ans=0;
for(int i=1;i<=n;i++) ans+=max(l[i],r[i]);
printf("%lld",ans+n);
return 0;
}
1055C
由裴蜀定理可知,(l_a) 和 (l_b) 每次能增多或减少的距离一定是 (gcd(t_a,t_b)) 的倍数。于是可以分别算出移动过程中 (l_a<l_b) 且 (l_b-l_a) 最小、(l_b<l_a) 且 (l_a-l_b) 最小的区间重叠个数,二者取 (max)。
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int main()
{
int la,ra,ta;
int lb,rb,tb;
scanf("%d%d%d%d%d%d",&la,&ra,&ta,&lb,&rb,&tb);
if(la<lb)
{
swap(la,lb);
swap(ta,tb);
swap(ra,rb);
}
int ans=0;
int g=gcd(ta,tb),zzt=(la-lb)/g;
// printf("g:%d
",g);
la-=zzt*g;ra-=zzt*g;
ans=max(ans,min(ra,rb)-max(la,lb)+1);
la-=g;ra-=g;
ans=max(ans,min(ra,rb)-max(la,lb)+1);
printf("%d",ans);
}
Day 3(1.27)
1017D
ZR 某次的普转提出过这个 trick。
注意到 (nle 12),考虑状压。枚举所有可能询问的 01 串并算出其与给出的 (m) 个串匹配出的 Wu 值(因为这 (m) 个串有可能会重复,所以需要去重),放到枚举的 01 串对应的 vector 里。将所有的 vector 排序,询问时二分即可。时间复杂度 (mathcal O(2^nn + 2^{2n}+qn)),可过。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int w[20];
struct node
{
int val,sta,cnt;
node(int vv,int ss){val=vv;sta=ss;cnt=0;}
node(){cnt=0;}
bool operator<(const node&x)const{return val<x.val;}
};vector<node> v[5000];
int cnt[5000],t[500010];
char s[20];
int main()
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=m;i++)
{
scanf("%s",s+1);
int len=strlen(s+1),ss=0;
for(int j=len;j;j--)
{
ss<<=1;
ss|=(s[j]=='1');
}
cnt[ss]++;
t[i]=ss;
// printf("ss:%d
",ss);
}
sort(t+1,t+m+1);m=unique(t+1,t+m+1)-t-1;
for(int i=0;i<(1<<n);i++)
{
for(int j=1;j<=m;j++)
{
int vv=0;
for(int k=1;k<=n;k++)
if((i&(1<<k-1))==(t[j]&(1<<k-1)))
vv+=w[k];
v[i].push_back(node(vv,t[j]));
}
}
for(int i=0;i<(1<<n);i++) sort(v[i].begin(),v[i].end());
for(int i=0;i<(1<<n);i++)
{
// printf("i==%d
",i);
for(int j=0;j<v[i].size();j++)
{
if(j) v[i][j].cnt+=v[i][j-1].cnt;
v[i][j].cnt+=cnt[v[i][j].sta];
}
}
for(int i=1;i<=q;i++)
{
int k;
scanf("%s%d",s+1,&k);
int len=strlen(s+1);
int ss=0;
for(int i=len;i;i--)
{
ss<<=1;
ss|=(s[i]=='1');
}
// printf("ss:%d
",ss);
int l=0,r=v[ss].size()-1;
int p=-1;
while(l<=r)
{
int mid=(l+r)/2;
if(v[ss][mid].val<=k)
{
p=mid;
l=mid+1;
}
else r=mid-1;
}
if(p==-1)printf("0
");
else printf("%d
",v[ss][p].cnt);
}
return 0;
}
1015E2
枚举能够成为十字中心所有点 ((i,j)),二分其十字大小,将十字的所有点标记。一趟流程走完后若还是有字符为 ( exttt{*}) 的点没被标记,则输出 -1
。
时间复杂度为 (mathcal O(n^3)) 加一个小于 (1) 的常数,Time Limit 是 3s,所以可过。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1000;
int sum[N+10][N+10];
char s[N+10][N+10];
int query(int stl,int str,int edl,int edr)
{return sum[edl][edr]-sum[edl][str-1]-sum[stl-1][edr]+sum[stl-1][str-1];}
struct Ans
{
int l,r,sz;
Ans(int ll,int rr,int szz)
{
l=ll;
r=rr;
sz=szz;
}
Ans(){}
};
bool vis[N+10][N+10];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(s[i][j]=='*');
// for(int i=1;i<=n;i++) row[i].Build(1,1,m);
// for(int i=1;i<=m;i++) col[i].Build(1,1,n);
vector<Ans> ans;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(s[i][j]=='.') continue;
int p=0;
int l=1,r=min(min(n-i,m-j),min(i-1,j-1));
if(r==0) continue;
int x=i,y=j;
while(l<=r)
{
int mid=(l+r)/2;
if(query(x-mid,y,x-1,y)==mid &&
query(x,y-mid,x,y-1)==mid &&
query(x+1,y,x+mid,y)==mid &&
query(x,y+1,x,y+mid)==mid)
{
p=mid;
l=mid+1;
}
else r=mid-1;
}
if(p)
{
for(int k=x-p;k<=x+p;k++)
vis[k][y]=1;
for(int k=y-p;k<=y+p;k++)
vis[x][k]=1;
ans.push_back(Ans(x,y,p));
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(s[i][j]=='*')
{
if(!vis[i][j])
{
printf("-1");
return 0;
}
}
}
}
printf("%d
",ans.size());
for(int i=0;i<ans.size();i++)
printf("%d %d %d
",ans[i].l,ans[i].r,ans[i].sz);
return 0;
}
1012C
感谢 https://www.luogu.com.cn/blog/cll523067/cf1012c-hills-ti-xie。
令 (f(i,j,0/1)) 表示前 (i) 个数,(j) 个数满足 (a_j>a_{j-1}) 且 (a_j>a_{j+1}),第 (i) 个数状态为 (0/1) 的情况,其中 (1) 表示满足 (a_i>a_{i+1}) 且 (a_i>a_{i-1}),(0) 表示不满足。状态为 (0) 的转移很显然,决策上一个点是否满足即可,(f(i,j,0)=min{f(i-1,j,0),f(i-1,j,1)+max{0,a_i-a_{i-1}+1}})。因为有可能不需要作任何操作就满足,所以要和 (0) 取一个 (max)。状态为 (1) 的转移有点难搞,需要观察到一个性质:选择的 (j) 个点一定不相邻,所以需要从 (a_{i-2}) 转移,决策它是否满足条件即可,转移方程为 (f(i,j,1)=min{f(i-2,j-1,1)+max{0,a_{i-1}-a_i+1,a_{i-1}-a_{i-2}+1},f(i-2,j,0)+max{0,a_{i-1}-a_i+1}})。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5000;
int dp[N+10][N+10][2];
int a[N+10];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
memset(dp,0x3f,sizeof(dp));
dp[0][0][0]=dp[1][0][0]=dp[1][1][1]=0;
a[0]=2147483647;
for(int i=2;i<=n;i++)
{
dp[i][0][0]=0;
for(int j=1;j<=(i+1)/2;j++)
{
dp[i][j][0]=min(dp[i-1][j][0],dp[i-1][j][1]+max(0,a[i]-a[i-1]+1));
dp[i][j][1]=min(dp[i-2][j-1][1]+max(0,max(a[i-1]-a[i]+1,a[i-1]-a[i-2]+1)),dp[i-2][j-1][0]+max(0,a[i-1]-a[i]+1));
}
}
for(int i=1;i<=(n+1)/2;i++) printf("%d ",min(dp[n][i][0],dp[n][i][1]));
return 0;
}
Day 4(1.28)
1000D
令 (f(i)) 表示以 (i) 开头的合法序列的个数,那么有:
其中 (mathrm C_{n-i}^{a_i}) 代表以 (i) 开头新开一个合法的段(题目中的 segment)的数量,(sumlimits_{j=i+1}^n mathrm{C}_{j-i-1}^{a_i}f(j)) 表示使用后面的合法段,并且 (isim j-1) 再接一个合法段的数量。
预处理组合数然后暴力转移即可,时间复杂度 (mathcal O(n^2))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
#define int long long
const int MOD=998244353,N=1e3;
int p[N+10],inv[N+10];
int qpow(int a,int n)
{
a%=MOD;int ans=1;
while(n)
{
// printf("ans:%d
",ans);
if(n&1) ans=ans*a%MOD;
a=a*a%MOD;
n>>=1;
}
return ans;
}
void init(int n)
{
inv[0]=p[0]=1;
for(int i=1;i<=n;i++)
{
p[i]=p[i-1]*i%MOD;
inv[i]=qpow(p[i],MOD-2);
}
}
int a[N+10];
int C(int n,int m)
{
// if(n==0) return 0;
if(m>n) return 0;
return p[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int dp[N+10];
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
init(n);
for(int i=n;i;i--)
{
if(a[i]<=0) continue;
for(int j=i+1;j<=n;j++)
{
dp[i]+=C(j-i-1,a[i])*dp[j]%MOD;
dp[i]%=MOD;
}
dp[i]+=C(n-i,a[i])%MOD;
dp[i]%=MOD;
}
int ans=0;
for(int i=1;i<=n;i++)
{
ans+=dp[i];
ans%=MOD;
}
printf("%lld",ans);
return 0;
}
1012B
如果将 (n) 行 (m) 列看成独立的 (n+m) 个点,将所有给出的 ((r_i,c_i)) 看作在并查集中合并 (r_i) 和 (c_i) 所在集合,你会发现:若给出三个点 ((x_1,y_1),(x_2,y_1),(x_1,y_2)),那么它们带来的效果就是将 (x_1,x_2,y_1,y_2) 都放到一个集合中,此时 (x_2) 和 (y_2) 在同一个集合!这样就达到了给出三个点 ((x_1,y_1),(x_2,y_1),(x_1,y_2)),迅速填出 ((x_2,y_2)) 的目的。不难发现,最终答案即为集合数量减一。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e5;
int f[N*2+10];
void init(int n) {for(int i=1;i<=n;i++) f[i]=i;}
int getf(int x) {return f[x]==x?x:f[x]=getf(f[x]);}
int main()
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
init(n+m);
for(int i=1;i<=q;i++)
{
int x,y;
scanf("%d%d",&x,&y);
f[getf(y+n)]=getf(x);
}
int ans=-1;
for(int i=1;i<=n+m;i++) ans+=f[i]==i;
printf("%d",ans);
return 0;
}
999D
神仙贪心,放官方 Tutorial 跑路。你们英语都比我好一定能读懂对不对
For each (i) from (0) to (m−1), find all elements of the array that are congruent to (i) modulo (m), and store their indices in a list. Also, create a vector called (free), and let (k) be (dfrac{n}{m}).
We have to cycle from (0) to (m−1) twice. For each (i) from (0) to (m−1), if there are in list too many (i.e., (>k)) elements congruent to (i) modulo (m), remove the extra elements from this list and add them to (free). If instead there are too few (i.e., (<k)) elements congruent to (i) modulo (m), remove the last few elements from the vector (free). For every removed index (idx), increase (a_{idx}) by ((i−a_{idx})mod m).
After doing so (after two passes), we print the total increase and the updated array.
It is obvious that after the first (m) iterations, every list will have size at most (k), and after (m) more iterations, all lists will have the same sizes. It can be easily proved that this algorithm produces an optimal answer.
The time complexity is (mathcal O(n+m)).
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=2e5;
int a[N+10];vector<int>c[N+10];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
c[a[i]%m].push_back(i);
}
vector<pair<int,int> > Free;
long long ans=0;
for(int i=0;i<2*m;i++)
{
int j=i%m;
while(c[j].size()>n/m)
{
Free.push_back(make_pair(i,c[j].back()));
c[j].pop_back();
}
while(!Free.empty() && c[j].size()<n/m)
{
pair<int,int> tmp=Free[Free.size()-1];Free.pop_back();
// c[j].push_back(tmp.second);
c[j].push_back(tmp.second);
a[tmp.second]+=i-tmp.first;
ans+=i-tmp.first;
}
}
printf("%lld
",ans);
for(int i=1;i<=n;i++) printf("%d ",a[i]);
return 0;
}
Day 5(1.29)
993B
令第一个人给出的 (2n) 个数为 (a_{1,0},a_{1,1},cdots,a_{n,0},a_{n,1}),第二个人给出的 (2m) 个数为 (b_{1,0},b_{1,1},cdots,b_{n,0},b_{n,1})。如果答案不为 -1
和 0
,那么枚举哪个数是公共数是一定有唯一解;如果答案为 0
,那么不论枚举哪个数都能在对方的数中找到符合条件的数对;如果上面两种情况都不满足,答案为 -1
。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[20][2],b[20][2],n,m;
int geta(int i,int k)
{
int cnt=0;
for(int j=1;j<=m;j++)
{
for(int p=0;p<=1;p++)
{
if(b[j][p]==a[i][k]&&b[j][p^1]!=a[i][k^1])
cnt++;
}
}
return cnt;
}
int getb(int i,int k)
{
int cnt=0;
for(int j=1;j<=n;j++)
{
for(int p=0;p<=1;p++)
{
if(a[j][p]==b[i][k]&&a[j][p^1]!=b[i][k^1])
cnt++;
}
}
return cnt;
}
int Count[10];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i][0]>>a[i][1];
if(a[i][0]>a[i][1]) swap(a[i][0],a[i][1]);
}
for(int i=1;i<=m;i++)
{
cin>>b[i][0]>>b[i][1];
if(b[i][0]>b[i][1]) swap(b[i][0],b[i][1]);
}
// if(n==1)
// {
// if(geta(1,0)>0&&getb(1,1)>0) cout<<0;
// else if(geta(1,0)>0)cout<<a[1][0];
// else cout<<a[1][1];
// return 0;
// }
// if(m==1)
// {
// if(getb(1,0)>0&&getb(1,1)>0)cout<<0;
// else if(getb(1,0)>0)cout<<b[1][0];
// else cout<<b[1][1];
// return 0;
// }
// for(int i=1;i<=n;i++)
// {
// printf("%d %d
",geta(i,0),geta(i,1));
// }
// int cnta=0,cntb=0;
for(int i=1;i<=n;i++)
{
Count[a[i][0]]+=geta(i,0);
Count[a[i][1]]+=geta(i,1);
}
for(int i=1;i<=m;i++)
{
Count[b[i][0]]+=getb(i,0);
Count[b[i][1]]+=getb(i,1);
}
int cnt=0;
for(int i=1;i<=9;i++) cnt+=(bool)Count[i];
if(cnt==1)
{
for(int i=1;i<=n;i++)
{
for(int j=0;j<=1;j++)
{
if(geta(i,j))
{
cout<<a[i][j];
return 0;
}
}
}
}
else
{
bool flag=1;
for(int i=1;i<=n;i++)
if(geta(i,0)>0&&geta(i,1)>0)
flag=0;
for(int i=1;i<=m;i++)
{
// printf("getb(%d,0):%d, getb(%d,1):%d
",i,getb(i,0),i,getb(i,1));
if((getb(i,0)>0&&getb(i,1)>0))
flag=0;
}
if(flag) cout<<0;
else cout<<-1;
}
return 0;
}
982D
并查集维护各个点之间是否联通,同时维护几个值:(cnt,cnt_1,maxl),分别表示区间个数,长度最长的区间个数,最长的区间长度,(a_1+1,a_2+1cdots a_n) (mathcal O(n)) 枚举 (k) 的大小,每次判断 (cnt) 是否 (=cnt_1),按照题意更新答案即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e5;
struct node
{
int v,p;
node(){}
node(int vv,int pp){v=vv;p=pp;}
bool operator<(const node&x)const{return v<x.v;}
}a[N+10];
int f[N+10],sz[N+10];bool vis[N+10];
void init(int n){for(int i=1;i<=n;i++){f[i]=i;sz[i]=1;}}
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
int main()
{
int n;
scanf("%d",&n);
init(n);
for(int i=1;i<=n;i++)
{
a[i].p=i;
scanf("%d",&a[i].v);
}
sort(a+1,a+n+1);
int cnt=0,cnt1=0,mx=0;
int k=0,maxc=0;
//区间总数,最大区间个数,最大区间值
for(int i=1;i<=n;i++)
{
vis[a[i].p]=1;
// for(int j=1;j<=n;j++)
// {
// if(vis[j]) printf("# ");
// else printf("* ");
// }
// putchar('
');
// for(int j=1;j<=n;j++)
// {
// printf("%d ",sz[getf(j)]);
// }
// putchar('
');
if(!vis[a[i].p-1]&&!vis[a[i].p+1])
cnt++;
if(!vis[a[i].p-1]&&vis[a[i].p+1])
{
sz[getf(a[i].p)]+=sz[getf(a[i].p+1)];
f[getf(a[i].p+1)]=getf(a[i].p);
}
if(!vis[a[i].p+1]&&vis[a[i].p-1])
{
sz[getf(a[i].p)]+=sz[getf(a[i].p-1)];
f[getf(a[i].p-1)]=getf(a[i].p);
}
if(vis[a[i].p-1]&&vis[a[i].p+1])
{
cnt--;
sz[getf(a[i].p)]+=sz[getf(a[i].p-1)];
f[getf(a[i].p-1)]=getf(a[i].p);
sz[getf(a[i].p)]+=sz[getf(a[i].p+1)];
f[getf(a[i].p+1)]=getf(a[i].p);
}
if(sz[getf(a[i].p)]>mx)
{
mx=sz[getf(a[i].p)];
cnt1=1;
}
else if(sz[getf(a[i].p)]==mx)cnt1++;
// printf("i:%d, cnt:%d, max:%d
",i,cnt,mx);
if(cnt==cnt1)
{
// printf("cnt:%d, a[i].v:%d
",cnt,a[i].v);
if(cnt1>maxc)
{
maxc=cnt1;
k=a[i].v+1;
}
}
}
printf("%d",k);
return 0;
}
981D
按位从高到低考虑。假设我们在考虑第 (i) 位,( ext{ans}) 为前面的位的答案,(s= ext{ans}+2^{i})。同时,令 (f(i,j)) 表示前 (i) 本书,摆 (j) 个书架是否能满足当前一位的要求((0/1)),显然有方程 (f(i,j)= ext{or}_{l=1}^i (f(l-1,j-1) ext{ and } ((sumlimits_{s=l}^i a_i) ext{ and }s=s))),( ext{or}) 表示按位或,( ext{and}) 表示按位与。
时间复杂度 (mathcal O(n^2klogsumlimits_{i=1}^n a_i))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int N=50;
ll a[N+10],sum[N+10];
bool dp[N+10][N+10];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int t=(int)(log(sum[n])/log(2))+1;
// printf("t:%d
",t);
ll ans=0;
for(int s=t;~s;s--)
{
ll zzt=ans+(1ll<<s);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
for(int l=1;l<=i;l++)
{
if(((sum[i]-sum[l-1])&zzt)==zzt)
dp[i][j]|=dp[l-1][j-1];
}
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=k;j++)
// printf("%d",dp[i][j]);
// putchar('
');
// }
// puts("---");
ans+=dp[n][k]*(1ll<<s);
}
printf("%lld",ans);
return 0;
}
Day 6(1.30)
965D
令 (a_0=0),则答案为 (minlimits_{i=l}^{w-1}{sumlimits_{j=i-w}^{i}a_j}),下面是证明。
假设所有的青蛙都在 (xsim x+l-1) 这个区间,现在需要将所有青蛙转移到 (x+1sim x+l)。两个区间的重叠部分 (x+1sim x+l-1) 的青蛙不动即可,其余处在 (x) 的青蛙需要转移到 (x+l)。由于石头的限制,只能转移 (min{a_x,a_{x+l}}) 只,加上中间的区间总共是 (min{sumlimits_{i=x}^{x+l-1} a_i,sumlimits_{i=x+1}^{x+l} a_i}) 只,像上面一样转移,一直转移到最后一个区间,最终能到达终点的青蛙就是 (minlimits_{i=l}^{w-1}{sumlimits_{j=i-w}^{i}a_j}) 只。
(mathcal{Q.E.D.})
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5;
int a[N+10],sum[N+10];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
int ans=0x7fffffff;
for(int i=m;i<n;i++) ans=min(ans,sum[i]-sum[i-m]);
printf("%d",ans);
return 0;
}
961E
一个数对 ((x,y)) 能对答案产生 (1) 的贡献,当且仅当 (a_xge y) 且 (a_yge x)。枚举 (x),使用堆(STL priority_queue)存储所有满足 (a_yle x) 的 ((y,a_y)),同时使用权值线段树维护满足上一条件的 (y) 的数量,查询时 ans += query(a[x] ~ n)
(在权值线段树查询大小在区间 ([a_x, n]) 的 (y) 的数量)即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=2e5;
int a[N+10];
typedef long long ll;
struct seg
{
int l,r,sum;
seg(){}
}t[N*4+10];//维护i
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
if(l==r) return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void modify(int p,int l,int d)
{
if(t[p].l==t[p].r)
{
t[p].sum+=d;
return;
}
int mid=(t[p].l+t[p].r)/2;
if(l<=mid)modify(p*2,l,d);
else modify(p*2+1,l,d);
t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
int query(int p,int l,int r)
{
if(l<=t[p].l&&t[p].r<=r) return t[p].sum;
int mid=(t[p].l+t[p].r)/2,ans=0;
if(l<=mid)ans+=query(p*2,l,r);
if(r>mid)ans+=query(p*2+1,l,r);
return ans;
}
struct node
{
int x,p;
node(){}
node(int xx,int pp){x=xx;p=pp;}
bool operator<(const node&rhs)const{return x>rhs.x;}
};
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=min(a[i],n);
}
build(1,1,n);
ll ans=0;
priority_queue<node> que;
// for(int i=1;i<=n;i++)
// for(int j=i+1;j<=n;j++)
// ans+=(a[i]>=j&&a[j]>=i);
for(int i=1;i<=n;i++)
{
while(!que.empty()&&que.top().x<i)
{
// printf("que.top():%d
",que.top());
node xx=que.top();
modify(1,xx.p,-1);
que.pop();
}
// printf("query(1,1,n):%d
",query(1,1,n));
ans+=query(1,1,a[i]);
que.push(node(a[i],i));
modify(1,i,1);
}
printf("%lld",ans);
return 0;
}
959E
用下面的代码暴力打表找规律:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int n;
struct Edge
{
int u,v,w;
Edge(){}
Edge(int x,int y,int z){u=x;v=y;w=z;}
bool operator<(const Edge&x)const{return w<x.w;}
}e[10000000];
int f[10000000];
void init(){for(int i=1;i<=n;i++)f[i]=i;}
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
int sol()
{
int m=0;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
e[++m]=Edge(i,j,(i^j));
sort(e+1,e+m+1);
init();
int ans=0;
for(int i=1;i<=m;i++)
{
int fx=getf(e[i].u),fy=getf(e[i].v);
if(fx==fy)continue;
f[fy]=fx;
ans+=e[i].w;
}
return ans;
}
int main()
{
freopen("zzt.txt","w",stdout);
for(n=1;n<=1000;n++) printf("%d ",sol());
}
打出来的表:
并不能发现什么规律,于是将这个表差分一下:
看起来很有规律的样子,但还是什么都看不出来,只好向题解低头。当你翻阅了 CF 的官方 Tutorial 后,你会发现:这不就是 lowbit 序列吗!然后就能找到规律:(源自官方 Tutorial)
显然,令 (f(x)=sumlimits_{i=1}^x operatorname{lowbit}(i)),那么显然有 (f(2^i-1)=2f(2^{i-1}-1)+2^{i-1})((i>1)),然后对 (n) 按位枚举,若第 (i) 位为 (1) 那么会对答案产生 (f(i)+2^i) 的贡献,累加即可。
由于上面的表实际上 (0sim n-1) 是 ( ext{lowbit}) 序列,所以要将 (n) 在输入时减一。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll f[50];
int main()
{
ll n;
scanf("%lld",&n);n--;
for(int i=1;(1ll<<(ll)i)<=n;i++)f[i]=2*f[i-1]+(1ll<<(i-1));
ll ans=0;
for(int i=0;(1ll<<(ll)i)<=n;i++)if(n&(1ll<<(ll)i))ans+=f[i]+(1ll<<(ll)i);
printf("%lld",ans);
return 0;
}