A题 题目链接 (签到)
题意:给定一个序列,问可以分成多少段,满足从1开始,遇到1或末尾元素结束。
比如 1 2 | 1 2 3 | 1 分成3段 1 | 1 | 1 | 1 分成4段
思路:直接找每个1,当前1的下标减去上一个1的下标就是一段的长度。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=30000000;
const double eps=1e-5;
const int maxn=4e5+50;
int n;
vector<int> ans;
int a[1005];
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
}
int p1=1;
for(int i=2;i<=n;i++)
{
if(a[i]==1)
{
ans.push_back(i-p1);
p1=i;//更新最近的1的位置
}
}
ans.push_back(n+1-p1);//末尾段
cout<<ans.size()<<'\n';
for(auto i:ans) cout<<i<<' ';
// system("pause");
return 0;
}
B题 题目链接 (字符串+模拟)
题意:给定两个字符串,我们可以每次选择1个字符串,从最左边删去一个字符,代价为1,问删至两个字符串相等的最少代价。
思路:由于这题是只能删最左边的,所以我们从最右边遍历找出最长的公共后缀。
然后len1+len2-公共后缀长*2 就是最少代价了
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=30000000;
const double eps=1e-5;
const int maxn=2e5+50;
char s1[maxn],s2[maxn];
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%s%s",s1+1,s2+1);
int len1=strlen(s1+1),len2=strlen(s2+1);
int same=0;//相同后缀长度
int p1=len1,p2=len2;
for(;p1>=1,p2>=1;p1--,p2--)
{
if(s1[p1]==s2[p2])
{
same++;
}
else break;
}
printf("%d\n",len1-same+len2-same);
// system("pause");
return 0;
}
C 题题目链接 (map的运用)
题意:给定一个长度为n的序列,问其是否满足 ,任意一个元素都能找到另一个元素与之相加,和为2的n次幂。
1<=n<=1e5+2e4 1<=ai<=1e9
思路:这题刚开始觉得应该用map写, 对于每一个元素,在输入的时候预处理,令 map[k-a[i]]=i (2<=k<=2^30),映射为i是为了后面判断的时候,防止出现自己+自己的情况,具体实现看代码 。如果k-a[i]这个值出现大于1次,就改变映射为INF,说明不需要判断自己+自己的情况了。
但是一直tle。最后发现问题在于k的枚举方式那里
int X=(int)pow(2,30);
for(int i=1;i<=n;i++)
{
for(int k=2;k<=X;k*=2)
{
if(k<a[i]) continue;
if(!mp[k-a[i]]) mp[k-a[i]]=i;//只有自己映射了
else mp[k-a[i]]=INF;
}
}
//这样枚举的话就tle
for(int i=1;i<=n;i++)
{
int k=1;
for(int j=1;j<=30;j++)
{
k*=2;
if(k<a[i]) continue;
if(!mp[k-a[i]]) mp[k-a[i]]=i;//只有自己映射了
else mp[k-a[i]]=INF;
}
}
//这也就ac了
AC代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
unordered_map<int,int> mp;
int n;
int a[maxn];
//(i & (i - 1)) == 0 说明i是2的n次幂
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
n=read();
if(n==1)
{
puts("1");
return 0;
}
for(int i=1;i<=n;i++)
{
a[i]=read();
}
for(int i=1;i<=n;i++)
{
int k=1;
for(int j=1;j<=30;j++)
{
k*=2;
if(k<a[i]) continue;
if(!mp[k-a[i]]) mp[k-a[i]]=i;//只有自己映射了
else mp[k-a[i]]=INF;//多次取到 就说明不会出现自己+自己=2的n次幂这种情况
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
int x=mp[a[i]];
if(!x || x==i )//不存在映射 以及 只有和自己相加才能得到2的n次幂
ans++;
}
printf("%d",ans);
// system("pause");
return 0;
}
D题 题目链接(贪心+dp)
题意:给定一串数,将其进行划分,输出最优划分下,最多的能被3整除的数字的个数。(不能含前导0)
思路:由贪心可知,如果某个位置的数是0 3 6 9 那么它一定要自己单独划分,因为其已经是3的倍数了。
基于贪心分析,发现前i个数可以由前i-1个数转移而来。
预处理:记录前缀和,前i个数的和
dp:
如果第i个数是3的倍数: dp[i]=dp[i-1]+1 用end_p记录最近的划分
否则向前枚举,如果枚举成功dp[i]= dp[i-1]+1,end_p=i ,枚举不成功dp[i]=dp[i-1]
具体转移过程见代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
char s[maxn];
bool isok[maxn];
int ans;
int sum[maxn];
int dp[maxn];//前i个数 对3取模为j 的最大个数
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)
{
int id=s[i]-'0';
sum[i]=sum[i-1]+id;//前缀和
isok[i]=!(id%3);//记录每位是否是3的倍数
}
int end_p=0;//记录最近的划分位置
dp[1]=isok[1];
for(int i=2;i<=len;i++)
{
if(isok[i])
{
end_p=i;
dp[i]=dp[i-1]+1;
}
else
{
dp[i]=dp[i-1];
for(int j=i-2;j>=end_p;j--)//枚举,从i-1 一直到end_p
{
int x=sum[i]-sum[j];
if(!(x%3))
{
end_p=i;//更新最近划分
dp[i]++;
break;//找到了一个就可以直接退出
}
}
}
}
printf("%d",dp[len]);
system("pause");
return 0;
}
E1 题目链接 (数学+思维)
题意:给定一个长度为n的数列{p1,p2,…,pn}由{1,2,…,n}组成。
求满足中位数为m的闭区间[l,r]的个数。
(当区间内元素为偶,中位数取中间靠左的那个数)
思路:
我们先从一个区间中,中位数的特性开始分析:
- [l,r]包含m ,m为中位数
- 记大于m的数的个数为n_max,小于m的数的个数为n_min,
3. n_max=n_min+0 / 1 也就是比m大的数 的个数 比 比m小的个数多0或1个
记m在[l,r]中的位置是index,
那么易得:记d1=n_max-n_min (区间 [index+1,r]中 )
d2= n_max-n_min (区间 [l,index-1]中 )
由等式3可知 d1+d2=0 或 1
那么,这题思路就比较清晰了,先找出m的下标index,然后遍历右边,如果a[i]>m ,令a[i]=1,小于则令其为-1。这样完成了一个类似区间优化的操作。
遍历右边的时候顺便记录sum[i],表示[index,i]的区间和,或者[i,index]的区间和,所以这里的区间和sum[i]就是区间[index+1,i]上的d1
再记录vis[i],表示index右边的区间中,区间和为i的区间个数 。由于i可能为负数,所以用maxn+i表示i
然后遍历index左边的元素,同样优化区间+记录区间和,这里的sum[i]就是区间[i,index-1]上的d2
由于 只要左边区间和+右边区间和=0/1 就满足条件,所以每次ans加上 vis[maxn+0-sum[i]+vis[maxn+1-sum[i]]即可
建议结合代码看,其实不难理解的~
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=2e5+7;
const double eps=1e-5;
const int maxn=2e5+50;
int n,m;
int a[maxn];
int sum[maxn];
int vis[maxn*2];
//7 8 3 2 5 1 4 6 m=5,遍历更新 vis[0]=1 vis[-1]=1 vis[-2]=1 vis[-1]=2
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
LL ans=0;
int cnt=0,index;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
if(a[i]==m)
{
index=i;
a[i]=0;
}
}
vis[maxn+0]++;//自身为1个区间
for(int i=index+1;i<=n;i++)
{
if(a[i]>m)
{
a[i]=1;
}
else if(a[i]<m)
{
a[i]=-1;
}
sum[i]=sum[i-1]+a[i];//[index,i]的区间和
vis[maxn+sum[i]]++;//加maxn 防止出现负数 表示区间和为sum[i]的区间数目,即d1=sum[i]的区间数
}
ans+=vis[maxn+0-0]+vis[maxn+1-0];//此时d2=0 d1-d2=0/1 分别有0-0 1-0两种情况
for(int i=index-1;i>=1;i--)
{
if(a[i]>m)
{
a[i]=1;
}
else if(a[i]<m)
{
a[i]=-1;
}
sum[i]=sum[i+1]+a[i];//这里sum[i]表示i到index的区间和
ans+=vis[maxn+0-sum[i]]+vis[maxn+1-sum[i]];
}
cout<<ans;
system("pause");
return 0;
}
F题 题目链接 (最短路+dfs)
题意:给定一个无向图,求出以1为顶点的k个最短路树,用01串标记每条边是否选择,如果不够k个,输出所有的选择方案
/最短路树: 根到其余顶点的路权和最小。
思路:先dijkstra跑一遍,用vector < int> pre[i]记录第i个点进入最短路时,它的前驱边有哪些。
记录完毕之后,对于除出发点外任意一个顶点,其前驱边可以任选一条,所以总的方案数就是 pre[i].size()累乘起来。
然后从第二个点开始dfs,每个点的每条前驱边选和不选标记一下 。最后选完n个点的前驱边就输出dfs的方案。
#pragma comment(linker, "/STACK:102400000,102400000")///手动扩栈
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f;
const double eps=1e-5;
const int maxn=2e5+50;
//题意:给定一个无向图,求出以1为顶点的k个最短路树,用01串标记每条边是否选择,如果不够k个,输出所有的
//思路:最短路树就是 根到其余顶点的路权和最小。 用dijkstra跑一边,vecotr记录每个顶点的所有前驱边
struct
{
int to,next;
}edge[maxn<<1];
struct node
{
int u,dis;
friend bool operator < (const node & f1,const node &f2)
{
if(f1.dis!=f2.dis) return f1.dis>f2.dis;//大于号 表示小的优先
else return f1.u>f2.u;
}
};//用于dij
int head[maxn],cnt=1;
int num[maxn<<1];//前向星建图的边是第几条边
void add(int from,int to,int i)
{
edge[cnt].to=to;
edge[cnt].next=head[from];
num[cnt]=i;
head[from]=cnt++;
}
vector<int> pre[maxn];//记录点的所有入边
bool vis[maxn];//dij
int dist[maxn];//用于dij
int n,m,k;
char choose[maxn];//记录方案
void dijkstra(int x)
{
fill(dist,maxn+dist,INF);
dist[x]=0;
priority_queue<node> q;
q.push({x,dist[x]});
while(!q.empty())
{
node temp=q.top();
q.pop();
if(vis[temp.u]) continue;
vis[temp.u]=1;
for(int i=head[temp.u];i;i=edge[i].next)
{
int to=edge[i].to;
if(vis[to]) continue;
if(dist[to]>dist[temp.u]+1)
{
pre[to].clear();
pre[to].push_back(num[i]);
dist[to]=dist[temp.u]+1;
q.push({to,dist[to]});
}
else if(dist[to]==dist[temp.u]+1)
{
pre[to].push_back(num[i]);//to的入边
}
}
}
}
void dfs(int v)
{
if(!k) return;
if(v>n)//选了了n个点,已经选了n-1条边
{
k--;
choose[m+1]='\0';//细节结尾,不然全给它输出喽
printf("%s\n",choose+1);//边从1开始
return;
}
for(int i=0;i<pre[v].size();i++)//枚举所有的入边
{
int e=pre[v][i];
choose[e]='1';
dfs(v+1);
choose[e]='0';
}
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d%d",&n,&m,&k);
fill(choose,choose+maxn,'0');//初始化成不选
for(int i=1;i<=m;i++)
{
int v,u;
scanf("%d%d",&v,&u);
add(v,u,i),add(u,v,i);
}
dijkstra(1);
int tot=1;
for(int i=2;i<=n;i++)
{
tot*=(int)pre[i].size();//总方案数
if(tot>=k) break;
}
k=min(tot,k);
printf("%d\n",k);
dfs(2);//每个点的每个前驱都有被选择的机会,选出n-1条边即可
system("pause");
return 0;
}