一、
1.maki和tree
链接
题目描述
有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。
这个树有 个顶点, 条边。每个顶点被染成了白色或者黑色。
maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?
注:
①树上两点简单路径指连接两点的最短路。
② <p,q>和<q,p> 的取法视为同一种。
第一行一个正整数 n。代表顶点数量。(1<=n<=100000)
第二行是一个仅由字符’B’和’W’组成的字符串。第 i个字符是B代表第 i个点是黑色,W代表第 i个点是白色。
接下来的 n-1行,每行两个正整数 x,y ,代表 x点和y 点有一条边相连
思路:dfs。先算出每一个白色联通块中有几个白色,将其全部赋值为总数,这样没找到一个黑色就可以直接算。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100005;
struct node{
int ne,to;
}e[N*2];
int fir[N],n,t,f[N],num[N];
vector<int> vec;
char s[N];
void add(int u,int v){
t++;
e[t].to=v;
e[t].ne=fir[u];
fir[u]=t;
}
void dfs(int x){
for(int i=fir[x];i!=-1;i=e[i].ne){
int v=e[i].to;
if(f[v]||s[v]=='B')continue;
f[v]=1;
vec.push_back(v);
dfs(v);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>s[i];
}
memset(fir,-1,sizeof(fir));
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(s[i]=='W'&&!f[i]){
vec.clear();
vec.push_back(i);
f[i]=1;
dfs(i);
for(int j=0;j<vec.size();j++){
num[vec[j]]=vec.size();
}
}
}
ll ans=0;
for(int i=1;i<=n;i++){
if(s[i]=='B'){
ll sum=0;
for(int j=fir[i];j!=-1;j=e[j].ne){
//cout<<"pp"<<endl;
int v=e[j].to;
ans+=num[v];
ans+=sum*num[v];
sum+=num[v];
}
}
}
cout<<ans<<endl;
return 0;
}
2.nozomi和字符串
题目描述
nozomi看到eli在字符串的“花园”里迷路了,决定也去研究字符串问题。
她想到了这样一个问题:
对于一个 “01”串而言,每次操作可以把0 字符改为 1 字符,或者把 1 字符改为 0 字符。所谓“01”串,即只含字符 0 和字符 1 的字符串。
nozomi有最多 次操作的机会。她想在操作之后找出一个尽可能长的连续子串,这个子串上的所有字符都相同。
nozomi想问问聪明的你,这个子串的长度最大值是多少?
注: 次操作机会可以不全部用完。
如果想知道连续子串的说明,可以去问问eli,nozomi不想再讲一遍。
思路1:二分长度,分(1,0)和(0,1)两种情况,取最大值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100005;
int n,k;
string s;
bool check1(int x){
int l=0,r=x-1;
string c=s;
int sum=0;
for(int i=l;i<=r;i++){
if(c[i]=='1'){
sum++;
}
}
int cnt=sum;
while(1){
r++;
if(r==n)break;
if(c[l]=='1')sum--;
l++;
if(c[r]=='1')sum++;
//if(c[l]=='1')sum++;
cnt=min(cnt,sum);
}
return cnt<=k;
}
bool check2(int x){
int l=0,r=x-1;
string c=s;
int sum=0;
for(int i=l;i<=r;i++){
if(c[i]=='0'){
sum++;
}
}
int cnt=sum;
while(1){
r++;
if(r==n)break;
if(c[l]=='0')sum--;
l++;
if(c[r]=='0')sum++;
//if(c[l]=='0')sum++;
cnt=min(cnt,sum);
}
return cnt<=k;
}
int main(){
scanf("%d%d",&n,&k);
cin>>s;
int l=1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(check1(mid))l=mid;
else r=mid-1;
}
int ans=0;
//cout<<l<<"kk"<<endl;
ans=max(ans,l);
l=1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(check2(mid))l=mid;
else r=mid-1;
}
ans=max(ans,l);
//cout<<l<<"kk"<<endl;
cout<<ans<<endl;
return 0;
}
思路2:尺取
当满足k时变换l,每次取最大值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
int n,k;
string s;
int cz(char a,char b){
int l=0,r=0,sum=0,ans=0;
for(int i=0;i<n;i++){
if(s[i]==a){
if(sum<k){
sum++;
r++;
}
else{
while(l<=r&&s[l]!=a)l++;
l++;
r++;
}
}
else r++;
ans=max(ans,r-l);
}
return ans;
}
int main(){
scanf("%d%d",&n,&k);
cin>>s;
int ans=max(cz('0','1'),cz('1','0'));
cout<<ans<<endl;
return 0;
}
3.nico和niconiconi
链接
nico平时最喜欢说的口头禅是niconiconi~。
有一天nico在逛著名弹幕网站"niconico"的时候惊异的发现,n站上居然有很多她的鬼畜视频。其中有一个名为《让nico为你洗脑》的视频吸引了她的注意。
她点进去一看,就被洗脑了:“niconicoh0niconico*^vvniconicoG(vniconiconiconiconiconicoG(vniconico…”
弹幕中刚开始有很多“nico1 nico2”等计数菌,但到后面基本上都是“计数菌阵亡”的弹幕了。
nico也想当一回计数菌。她认为:“nico” 计 a分,“niconi” 计b 分,“niconiconi” 计 c分。
她拿到了一个长度为 的字符串,请帮她算出最大计数分数。
注:已被计数过的字符不能重复计数!如"niconico"要么当作"nico"+“nico"计a+a分,要么当作"niconi”+"co"计 b分。
思路:简单01背包变形
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int N=300005;
char s[N];
ll n,a,b,c;
ll dp[N];
int main(){
scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
for(int i=1;i<=n;i++){
cin>>s[i];
}
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];
if(i>=4){
if(s[i]=='o'&&s[i-1]=='c'&&s[i-2]=='i'&&s[i-3]=='n'){
dp[i]=max(dp[i],dp[i-4]+a);
}
}
if(i>=6){
if(s[i]=='i'&&s[i-1]=='n'&&s[i-2]=='o'&&s[i-3]=='c'&&s[i-4]=='i'&&s[i-5]=='n'){
dp[i]=max(dp[i],dp[i-6]+b);
}
}
if(i>=10){
if(s[i]=='i'&&s[i-1]=='n'&&s[i-2]=='o'&&s[i-3]=='c'&&s[i-4]=='i'&&s[i-5]=='n'&&s[i-6]=='o'&&s[i-7]=='c'&&s[i-8]=='i'&&s[i-9]=='n'){
dp[i]=max(dp[i],dp[i-10]+c);
}
}
}
cout<<dp[n]<<endl;
return 0;
}
二、
1.算概率
链接
逆元情况下给的概率可以直接算。
dp。
dp[i][j]表示前i个数有j个正确的概率。
初始化每一个dp[i][0].
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=2005;
int n;
ll a[N],f[N][N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
f[0][0]=1;
for(int i=1;i<=n;i++){
f[i][0]=(f[i-1][0]*(1-a[i]+mod)%mod)%mod;
for(int j=1;j<=i;j++){
f[i][j]=((f[i-1][j]*(1-a[i]+mod)%mod)%mod+f[i-1][j-1]*a[i]%mod)%mod;
}
}
for(int i=0;i<=n;i++){
if(i==n)printf("%lld\n",f[n][i]);
else printf("%lld ",f[n][i]);
}
return 0;
}
2.拿物品
链接
贪心
每个人拿走一个物品,得到的利益都是a+b,比如牛牛他得到了a也让可乐损失了b,所以贪心排个序即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=200005;
int n;
struct node{
ll a,b,s,id;
}p[N];
ll ans1[N],ans2[N];
bool cmp(node x,node y){
return x.s>y.s;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&p[i].a);
p[i].id=i;
}
for(int i=1;i<=n;i++){
scanf("%lld",&p[i].b);
p[i].s=p[i].a+p[i].b;
}
sort(p+1,p+1+n,cmp);
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++){
if(i&1)ans1[++cnt1]=p[i].id;
else ans2[++cnt2]=p[i].id;
}
for(int i=1;i<=cnt1;i++){
if(i==cnt1)printf("%lld\n",ans1[i]);
else printf("%lld ",ans1[i]);
}
for(int i=1;i<=cnt2;i++){
if(i==cnt2)printf("%lld\n",ans2[i]);
else printf("%lld ",ans2[i]);
}
return 0;
}
3.施魔法
链接
简单dp
两种选择,一是连接在上一个后面,另一个是开辟新的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=300005;
const int inf=0x3f3f3f3f;
int n,k;
ll a[N],f[N];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
f[i]=inf;
}
sort(a+1,a+1+n);
f[k]=a[k]-a[1];
for(int i=k+1;i<=n;i++){
f[i]=min(f[i-1]+a[i]-a[i-1],f[i-k]+a[i]-a[i-k+1]);
}
cout<<f[n]<<endl;
return 0;
}
4.建通道
链接
二进制
具题意,两个相同的点的路费为0,所以要先去重,当找到一个01对的时候意味着找到两个点之间最短的路费,后面的这个位置是1的连到01对的0上,是0的连到1上,所以这(sm-1)条边全部是最短路费。
这里找01对不用循环套循环(会t),只需要看看0或者1的数目是否为0或者是sm。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=200005;
int n,k;
ll v[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&v[i]);
}
sort(v+1,v+1+n);
int sm=unique(v+1,v+1+n)-(v+1);
if(sm==1){
printf("0\n");
return 0;
}
for(int i=0;i<30;i++){
int sum=0;
for(int j=1;j<=sm;j++){
if(v[j]>>i&1)sum++;
}
if(sum>0&&sum<sm){
ll res=((ll)sm-1ll)*(1ll<<i);
printf("%lld\n",res);
break;
}
}
return 0;
}
三、
1、牛牛的Link Power II
链接
线段树
思路:如何在前后两个区域建立联系,不妨模拟一下,首先我们知道两点之间的距离是pos2-pos1,另一边有几个数代表这边的数要贡献几次。所以线段树中记录一块区域的1的个数,1的pos的总和还有答案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=100005;
int n,m;
char s[N];
struct node{
ll sum,psum,num;
int l,r;
}t[N*4];
void pushup(int cnt){
t[cnt].num=t[cnt<<1].num+t[cnt<<1|1].num;
t[cnt].psum=(t[cnt<<1].psum+t[cnt<<1|1].psum)%mod;
t[cnt].sum=((t[cnt<<1].sum+t[cnt<<1|1].sum)%mod+(t[cnt<<1].num*t[cnt<<1|1].psum%mod+mod-t[cnt<<1|1].num*t[cnt<<1].psum%mod)%mod)%mod;
}
void build(int cnt,int l,int r){
t[cnt].l=l;
t[cnt].r=r;
if(l==r){
if(s[l]=='1'){
t[cnt].psum=l;
t[cnt].sum=0;
t[cnt].num=1;
}
else{
t[cnt].num=t[cnt].psum=t[cnt].num=0;
}
return;
}
int mid=(l+r)>>1;
build(cnt<<1,l,mid);
build(cnt<<1|1,mid+1,r);
pushup(cnt);
}
void update(int cnt,int pos,int val){
if(t[cnt].l==t[cnt].r){
if(val){
t[cnt].psum=pos;
t[cnt].sum=0;
t[cnt].num=1;
}
else{
t[cnt].num=t[cnt].psum=t[cnt].num=0;
}
return;
}
int mid=(t[cnt].l+t[cnt].r)>>1;
if(pos<=mid)update(cnt<<1,pos,val);
else update(cnt<<1|1,pos,val);
pushup(cnt);
}
int main(){
scanf("%d",&n);
scanf("%s",s+1);
build(1,1,n);
scanf("%d",&m);
int q,pos;
printf("%lld\n",t[1].sum);
while(m--){
scanf("%d%d",&q,&pos);
if(q==1){
update(1,pos,1);
}
else update(1,pos,0);
printf("%lld\n",t[1].sum);
}
return 0;
}
2.牛牛的宝可梦Go
floyed+dp
在图上的dp问题 dp[i]表示在前i个宝可梦且必选i的情况下的最大值,最多200个宝可梦
注意初始化dp数组
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=205;
const int inf=0x3f3f3f3f;
int n,m,dis[N][N],k;
struct node{
int t,p;
ll val;
}a[100005];
ll dp[100005];
bool cmp(node x,node y){
if(x.t==y.t)return x.val>y.val;
return x.t<y.t;
}
int main(){
scanf("%d%d",&n,&m);
memset(dis,inf,sizeof(dis));
for(int i=1;i<=n;i++){
dis[i][i]=0;
}
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
dis[u][v]=dis[v][u]=1;
}
for(int p=1;p<=n;p++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][p]+dis[p][j]);
}
}
}
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d%d%lld",&a[i].t,&a[i].p,&a[i].val);
}
a[0]={0,1,0};
sort(a+1,a+1+k,cmp);
ll ans=0;
for(int i=1;i<=k;i++){
for(int j=1;j<=200;j++){
if(i-j<0)continue;
if(a[i].t-a[i-j].t>=dis[a[i].p][a[i-j].p]){
dp[i]=max(dp[i],dp[i-j]+a[i].val);
}
}
ans=max(ans,dp[i]);
}
printf("%lld\n",ans);
return 0;
}
四、
1.树上博弈
链接
思路:当两个人之间的距离是偶数的时候先手一定胜,因为一定会把后手逼到叶子节点上,而先手可以顺着后手的路走。奇+奇=偶,偶+偶=偶
这里最好dfs求深度
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=1000005;
const int inf=0x3f3f3f3f;
int n;
int dep[N];
ll cnt[2];
int main(){
scanf("%d",&n);
dep[1]=0;
int f;
cnt[0]++;
for(int i=2;i<=n;i++){
scanf("%d",&f);
dep[i]=dep[f]^1;
cnt[dep[i]]++;
}
cout<<cnt[0]*(cnt[0]-1)+cnt[1]*(cnt[1]-1)<<endl;
return 0;
}
2.坐火车
链接
思路:线段树或树状数组。以颜色建树。
以树状数组为例,x可以由x-1推过来,用一个前缀和后缀数组保存x前后不同颜色的个数方便与x和x-1做匹配。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=500005;
const int inf=0x3f3f3f3f;
int n;
struct node{
int col,l,r;
}a[N];
int pre[N],suf[N];
ll t[N];
int lowbit(int x){
return x& -x;
}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)){
t[i]+=v;
}
}
ll query(int x){
ll ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=t[i];
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a[i].col,&a[i].l,&a[i].r);
suf[a[i].col]++;
}
for(int i=1;i<=n;i++){
add(a[i].col,-pre[a[i].col]);
pre[a[i].col]++;
cout<<query(a[i].r)-query(a[i].l-1)<<" ";
suf[a[i].col]--;
add(a[i].col,suf[a[i].col]);
}
return 0;
}
3.匹配星星
链接
贪心。对x从小到大排序,将z为0的放入集合,当z为1时查询,这里用二分查询。
multiset可以对集合中的元素排好序并且可以存在重复元素
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=100005;
const int inf=0x3f3f3f3f;
int n;
struct node{
int x,y,z;
}a[N];
bool cmp(node a,node b){
if(a.x==b.x&&a.y==b.y)return a.z<b.z;
if(a.x==b.x)return a.y>b.y;
return a.x<b.x;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
}
int ans=0;
multiset<int> s;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
if(a[i].z){
multiset<int> ::iterator it;
it=s.lower_bound(a[i].y);
if(it!=s.begin()){
it--;
s.erase(it);
ans++;
}
}
else s.insert(a[i].y);
}
cout<<ans<<endl;
return 0;
}
五、
1.牛牛战队的比赛地
链接
简单三分。
由题意可得x到各个点的距离和是由大变小再变大的,符合三分条件。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=100005;
const double inf=0x3f3f3f3f;
const double eps=1e-7;
int n;
double ans;
struct node{
double x,y;
}a[N];
double check(double x){
double d=0;
for(int i=1;i<=n;i++){
double p=sqrt((a[i].x-x)*(a[i].x-x)+a[i].y*a[i].y);
d=max(d,p);
}
return d;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&a[i].x,&a[i].y);
}
double l=-10000,r=10000;
ans=inf;
while(r-l>eps){
double lmid=l+(r-l)/3.0;
double rmid=r-(r-l)/3.0;
if(check(lmid)<check(rmid))r=rmid;
else l=lmid;
}
printf("%.4lf\n",check(l));
return 0;
}
2.牛牛与牛妹的约会
链接
两种方式进行比较就好了,注意给出的是速度,所以不足一个单位长度也可以计算
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=100005;
const double inf=0x3f3f3f3f;
const double eps=1e-7;
int n;
int main(){
int t;
scanf("%d",&t);
double a,b;
while(t--){
scanf("%lf%lf",&a,&b);
double ans=0;
while(1){
double k1;
if(a<0){
k1=-pow(-a,1.0/3.0);
}
else k1=pow(a,1.0/3.0);
if(1+fabs(k1-b)<fabs(a-b)){
ans+=1.0;
a=k1;
}
else{
ans+=fabs(a-b);
break;
}
if(a==b)break;
}
printf("%.9lf\n",ans);
}
return 0;
}
六、
1.图
链接
tarjan
先tarjan出来每个环中的数目,再dfs找如果与环相连就+1.
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<cmath>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=1000005;
const double inf=0x3f3f3f3f;
const double eps=1e-7;
int n,e[N],dfn[N],low[N],tot,vis[N],id,ans,bh[N],num[N];
stack<int> s;
void tarjan(int x){
low[x]=dfn[x]=++tot;
s.push(x);
vis[x]=1;
int v=e[x];
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
if(low[x]==dfn[x]){
id++;
int cnt=0,now;
vis[x]=0;
do{
now=s.top();s.pop();
vis[now]=0;
bh[now]=id;
cnt++;
}while(now!=x);
num[id]=cnt;
}
}
int res[N];
int dfs(int x){
if(e[x]==x)return 1;
if(res[x])return res[x];
if(num[bh[x]]>1)return res[x]=num[bh[x]];
return res[x]=dfs(e[x])+1;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&e[i]);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
ans=max(ans,dfs(i));
}
cout<<ans<<endl;
return 0;
}