区间选点、猫猫向前冲、班长竞选、HRZ 的序列、HRZ 学英语、咕咕东的奇妙序列
作业:
区间选点:
还是那道熟悉的区间选点,可是跟原来的题意不一样了,现在是每个区间要求的点数不同,并且要求用差分约束系统的解法解决这道题。
这道题我觉得难就难在你怎么把他构建成图,用差分这个想法太奇特了,很不容易想到。但是题目既然已经讲了也就难度降低了。
思路大概就是:以s[i]表示i点之前有多少个点。然后某个区间**[a,b]要求点数>= c[i]** 就表示为s[b]-s[a]>=c[i]。然后以s[b]、s[a]、c[i]为图中的起点u,终点v和权重w构建图,然后运行spfa即可。但是这里需要注意为了约束0=<s[i]-s[i-1]<=1,需要在加入两组边 (前向星数组一定要开大一点……
在spfa中松弛条件需要改变,我们加入的边的形式为s[v]-s[u]>=w ,在这里我们要求最小的s[v]。也就是说若有x、y 使得不等式dis[v]-dis[u]>=x dis[v]-dis[u]>=y 且x<y成立 ,则必须满足dis[v]必须满足dis[v]-dis[u]>=y (较大的一个 即更新 dis[v]为dis[u]+y;
#include<iostream>
#include<queue>
#define maxn 110000
#define inf 0x3f3f3f3f
using namespace std;
struct Edge{
int v,w,next;
};Edge edge[maxn*2];
int head[maxn*2];int tot;
void add(int u,int v,int w){
edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot++;
}
int vis[maxn];
int dis[maxn];
queue<int >q;
void spfa(int s)
{
int u,v,w;
memset(dis,-inf,sizeof(dis));//最长路要用-inf
q.push(s);vis[s]=1;dis[s]=0;
while(!q.empty())
{
u=q.front();q.pop();
for(int i=head[u];i != -1;i=edge[i].next)
{
v=edge[i].v;w=edge[i].w;
if(dis[v]<dis[u]+w) //边的形式为s[v]-s[u]>=w 要求最小的s[v] ;
{ //也就是说若有节点x、y 使得不等式dis[v]-dis[u]>=x dis[v]-dis[u]>=y 且x<y成立
dis[v]=dis[u]+w; //则必须满足dis[v]必须满足dis[v]-dis[u]>=y (较大的一个 即更新 dis[v]为dis[u]+y;
if(vis[v]==0){vis[v]=1;q.push(v);}
}
}
vis[u]=0;
}
}
int main(){
memset(head,-1,sizeof(head));
int T;cin>>T;
int up=0;int begin=inf;
while(T--)
{
int a,b,c;cin>>a>>b>>c; //s[b]-s[a]>=c 化为 s[a]+c<=s[b] 即 u:s[a] v:s[b] w:c
add(a,b+1,c);
begin=min(a,begin);
up=max(up,b+1);
}
for(int i=begin+1 ; i<=up ; i++) //要满足0=<s[i]-s[i-1]<=1 (a)
{ //加入这样的边 u->v w(u,v)=0/1
add(i-1,i , 0); //(a)式化为 s[i-1]<=s[i] s[i]<=s[i-1]+1 即加入两组边u:i-1 v:i w:0 u:i v:i-1 w:1
add(i ,i-1,-1);
}
spfa(begin);
cout<<dis[up]<<endl;
return 0;
}
猫猫向前冲
这道题是一道拓扑排序的题目,数据结构课程也已经讲过了,具体思想是:
从入度为零的某个点开始,将其邻接点入度减一,再继续从入度为零的点进行改算法,直到遍历了所有点即可,
但是这道题,还是wa了好长时间,因为没看到多组数据🙃
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define maxn 510
int n,m;
struct Edge{
int v,w,next;
};
Edge edge[maxn*maxn];
int head[maxn*10];int tot;
void add(int u,int v,int w){
edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot++;
}
int ind[maxn];
priority_queue<int>q;
queue<int> ans;
kahn()
{
while(!q.empty())
{
int u=-1*q.top();ans.push(u); q.pop();
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;ind[v]--;
if(ind[v]==0)q.push(-1*v);
}
}
bool mark=false;
while(ans.size())
{
if(mark==false)
{cout<<ans.front();mark=true;}
else cout<<" "<<ans.front();
ans.pop();
} cout<<endl;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF)//注意........多组数据
{
memset(edge,0,sizeof(edge));
memset(ind,0,sizeof(ind));
memset(head,-1,sizeof(head));
int a,b;
int vis[502][502];memset(vis,0,sizeof(vis));
while(m--)
{
cin>>a>>b;
if(vis[a][b]==0){add(a,b,1);vis[a][b]=1;ind[b]++;}
}
for(int i=1;i<=n;i++)if(ind[i]==0)q.push(-1*i);
kahn();
}
return 0;
}
班长竞选
这道题是一道连通图的题目,因为题目中并没有保证不含环,所以可能会有强连通子图存在。
首先是求连通子图:这里用到了 kosaraju算法。算法的思想大概是:正着dfs一遍以求除后序序列,然后以后序序列的倒序开始再次dfs标记连通分量。如此我们得到了每个点及其对应的连通分量标记。
然后我们需要将每个连通分量缩为一个点,在这里,我默认将这个点的标号设为我们缩点过程中遇到连通分量中的第一个。具体缩点方法是:判断这条边的两个点在不在一个连通分量内。不在,我们就加入,其权重为点的数目(此时存储的为反向图。
然后,我们从这个反向图的入度为0的点开始dfs搜索所有它能到达的点。这里是因为票数最多的人一定不会再投票给别人,(由于票数的传递性。也可以用bfs。这样就解决了。(最后再判断一下票数相同的人定一下输出顺序即可
#include<iostream>
#include<queue>
#include<stack>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 5010
#define maxm 30010
int T,n,m;
struct Edge{
int v,w,next;
};
Edge edge[maxm],edge1[maxm],edge2[maxm];
int head[maxn],head1[maxn],head2[maxn];int tot,tot1,tot2;
void add(int u,int v,int w){
edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot++;
}
void add1(int u,int v,int w)
{
edge1[tot1].v=v;
edge1[tot1].w=w;
edge1[tot1].next=head1[u];
head1[u]=tot1++;
}
void add2(int u,int v,int w)
{
edge2[tot2].v=v;
edge2[tot2].w=w;
edge2[tot2].next=head2[u];
head2[u]=tot2++;
}
int cnt,kind;
int vis[maxn],post[maxn],group[maxn],gsum[maxn],ans[maxn];
void dfs(int s)
{
vis[s]=1;
for(int i=head[s];i!=-1;i=edge[i].next)
{
if(vis[edge[i].v]==0)dfs(edge[i].v);
}
cnt+=1;
post[cnt]=s;
return ;
}
void dfs1(int s)
{
group[s]= kind;gsum[kind]++;
for(int i=head1[s];i!=-1;i=edge1[i].next)
{
if(group[edge1[i].v]==0)dfs1(edge1[i].v);
}
return ;
}
int dfs2(int s)
{
vis[s]=1;
int re=0;
for(int i=head2[s];i!=-1;i=edge2[i].next)
{
if(vis[edge2[i].v]==0)
{
int k=dfs2(edge2[i].v);re=re+edge2[i].w+k;
}
}
return re;
}
void kosaraju()
{
for(int i=1;i<=n;i++)
if(vis[i]==0)
dfs(i);
for(int i=n;i>0;i--)
if(group[post[i]]==0)
kind=post[i],dfs1(post[i]);//以该scc中遇到的第一个编号为组号
}
//group[] kind gsum[kind] 组号kind代表的scc组有几个点
void solve()
{
int ind[maxn];memset(ind,0,sizeof(ind));
int mark[maxn];memset(mark,0,sizeof(mark));
for(int i=0;i<=n;i++)
for(int j=head[i];j!=-1;j=edge[j].next)
{
mark[edge[j].v]=1;mark[group[i]]=1;
if(group[i]!=group[edge[j].v])
{
add2( group[edge[j].v] , group[i] , gsum[group[i]] );
ind[group[i]]++;
}//缩点 存储反向图
}
for(int i=0;i<=n;i++)
if(ind[i]==0&&mark[i]==1)
{ memset(vis,0,sizeof(vis));
ans[i]=dfs2(i);ans[i]=ans[i]+gsum[i]-1;
}
}
void init()
{
tot=0;tot1=0;tot2=0;cnt=0;kind=1;
memset(edge,0,sizeof(edge));
memset(edge1,0,sizeof(edge1));
memset(edge2,0,sizeof(edge2));
memset(head,-1,sizeof(head));
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
memset(post,0,sizeof(post));
memset(vis,0,sizeof(vis));
memset(group,0,sizeof(group));
memset(ans,0,sizeof(ans));
memset(gsum,0,sizeof(gsum));
}
int main(){
cin>>T;int num=0;
while(T--)
{
num++;
init();
cin>>n>>m; int a,b;
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
add(a+1,b+1,1);
add1(b+1,a+1,1);
}
kosaraju();
solve();
stack< pair<int,int> >q;q.push(make_pair(-1,-1));
for(int i=0;i<=n;i++)
if(ans[i]>=q.top().second) q.push(make_pair(i,ans[i]));
int firi=0,fir=0;bool mar=0;
int anss[maxn];memset(anss,0,sizeof(anss));int to=0;
while(!q.empty())
{
firi=q.top().first;
fir=q.top().second;
q.pop();
if(fir==q.top().second)
{
for(int i=1;i<=n;i++)
if(group[i]==firi)
{
anss[to++]=i-1;
}
}
else{
for(int i=1;i<=n;i++)
if(group[i]==firi)
{
anss[to++]=i-1;
}
break;
}
}
sort(anss,anss+to);
if(mar==0)cout<<"Case "<<num<<": "<<fir<<endl;
for(int i=0;i<to;i++)
if(mar==1)cout<<" "<<anss[i];
else mar=1,cout<<anss[i];
cout<<endl;
}
return 0;
}
HRZ 的序列
原题没了,题意大概就是判断一堆数中 不同的数字情况,符合题目要求的情况有:
1、所有数字都一样
2、只能分两组
3、能分三组 且符合 y、 y+k、y+2*k。
push完了之后判断即可
#include<bits/stdc++.h>
using namespace std;
long long int a[10010];
int main(){
int T,n;
cin>>T;
while(T--)
{
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n);
queue<long long int >q;
while(!q.empty())q.pop();
q.push(a[0]);
for(int i=1;i<n;i++)
{
long long int t=q.back();
if(a[i]!=t)q.push(a[i]);
}
if(q.size()>3){
cout<<"NO"<<endl;
}
if(q.size()==1)
{
cout<<"YES"<<endl;
}
if(q.size()==2)
{
cout<<"YES"<<endl;
}
if(q.size()==3)
{
int a=q.front();q.pop();
int b=q.front();q.pop();
int c=q.front();q.pop();
int x=abs(a-b);int y=abs(b-c);
if(x==y)
{
cout<<"YES"<<endl;
}
else{
cout<<"NO"<<endl;
}
}
}
return 0;
}
HRZ 学英语
这道题题意大概是:给你一个字符串,字符串只包含AB…XYZ.和?,其中?可以指代任何一个字符串,找出第一个符合要求的子串:包含26个不同的字母(其中若有?则尽量指代较小的字符)
这道题我是用了队列的方式,维护一个queue,每次加入一个字符,判断这个字符是否在队列里,不在加进去。在的话就从队列开始删除,直到删掉原本队列中存在的这个字符,同时更新vis。最后输出的时候如果有?则从vis中从较小的开始找不在子串里的字符输出即可。
#include<bits/stdc++.h>
using namespace std;
string s;
queue<char>q;
int vis[100];
char t[30];
int mark;
queue<char>r;
int main(){
cin>>s;int n=s.length();
for(int i=0;i<n;i++)
{
if(vis[s[i]]==0){ q.push(s[i]); if(s[i]!='?') vis[s[i]]=1;}
else{
while(q.front()!=s[i]){vis[q.front()]=0;q.pop();}vis[q.front()]=0;q.pop();
q.push(s[i]);vis[s[i]]=1;
}
if(q.size()==26)
{
for(char j='A';j<='Z';j++)
if(vis[j]==0){r.push(j);}
mark=1;break;
}
//OPQRSTUVW??MABC??FGHIJK???OPQR?TUVWXY?
//OPQRSTUVW de MABC ln FGHIJK xyz
}
while(mark==1&&q.size()){
if(q.front()!='?'){cout<<q.front();q.pop();}
else{
cout<<r.front();r.pop();q.pop();
}
}
if(mark==0)
{
cout<<-1;
}
}
咕咕东的奇妙序列
题意大概是一个这样的序列:
1121231234…12345678910…
然后我们要找到这个序列中第K个字符是0-9中哪一个字符。
题意很简单,但是做起来感觉很绕。
我们可以将这个字符串分为如下形式的n段:
1
12
....
....
123456789
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
12345678910
......
12345678..979899
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
12345678..979899100
....
12345678..979899100...999
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
12345678..979899100...9991000
....
12345678..979899100...9991000...9999
简单来说
第一步先找出这个k在哪一段(这个段指的是最大数字是几位数,类似123456…2425则在最大数字为2位数的那一段。)
第二步找出这个k所在的那一串(如123456…2425在最大数字为25的这一串)
第三步再从这一串里找到属于哪一个整数 (如123456…2425中加粗的4属于24这个整数。
这里我用ss[i]存储i+1位数的最后一个数往前全部加起来的数列长度,用s[i]存储i+1位数最后一个数的那组的位数。
然后利用ss和s第一步完成后,用二分进行第二步。找到那个数段。
第三步我是先算出该字符串属于的数的 位数-1前数字的总数。然后位次减去该总数 进行取余位数算位次即可
#include<iostream>
#include<string>
using namespace std;
typedef unsigned long long int ull;
ull num[20];
ull a[200], s[200], ss[200];//ss[i]为i+1位数的最后一个数往前全部加起来的数列长度 ss[3] 1、12、、、、、123..9999
void pre() //s[0] 9 s[1] 99 s[2] 999 s[3] 9999 s[i] 10^(i+1)-1
{
num[0] = 0; //s[i]为i+1位数最后一个数的那组的位数 s[0] 9 s[1] 189 s[2] 2889 s[3] 38889
ss[0] = 45; ull h = 9; s[0] = 9;//
for (ull i = 1; i < 20; i++)
{
num[i] = num[i - 1] * 10 + 9;
if (ss[i] >= 1e18)
break;
else
{
h = h * 10;
ss[i] = ss[i - 1] + h * s[i - 1] + (i + 1) * h * (1 + h) / 2;
s[i] = s[i - 1] + (i + 1) * h;
}
}
}
void co()//测试用
{
for (int i = 0; i < 20; i++)
cout << num[i] << " " << s[i] << " " << ss[i] << endl;
}
void solve(ull q)
{
ull id = 1;
while (q > ss[id - 1]) { id++; } //q在 最大id位数 的 一组的 范围内 即【最小1 , 最大【10^(id-1)~~~~10^(id)-1】 】
//找这组数 最大数是【10^(id-1)~~~~10^(id)-1】内的哪个数
ull k = q - ss[id - 2];//k为除去前一阶段的位次
if (id == 1)k = q;
ull l = num[id - 1] + 1, r = num[id];
ull judge = 0, mid = 0;
ull tt;
while (l + 1 < r)
{
mid = (l + r) >> 1;
//算出mid所对应的位次 并与K比较
tt = (mid - num[id - 1]);
if (id >= 2) judge = tt * s[id - 2] + id * (1 + tt) * tt / 2;
if (id == 1) judge = (1 + tt) * tt / 2;
if (judge < k)
{
l = mid;
}
else {
r = mid;
}
}
if (id >= 2) {
ull temp = ((l - num[id - 1]) * s[id - 2] + id * (1 + (l - num[id - 1])) * (l - num[id - 1]) / 2);
if (temp < k) mid = r; else mid = l;
}
else {
if ((1 + l) * l / 2 < k)mid = r; else mid = l;
}
//找到了位数在123.....mid这一段中
//算出在这一段的位置新k
ull bef = (mid - 1 - num[id - 1]) * s[id - 2] + id * (1 + (mid - 1 - num[id - 1])) * (mid - 1 - num[id - 1]) / 2;
k = k - bef;
//k为 所求数位 在123.....mid段中的位置
ull sum = 0; ull ans = 0; ull h = 9; ull mark = 1, markk = 1;
for (ull i = 1; i <= id; i++)
{
if (sum + h * i >= k) { mark = i; k = k - sum; break; }
else sum = sum + h * i;
h = h * 10;
}
//k在mark位数中的位置
ull ys = k / mark; for (ull i = 1; i < mark; i++)markk = markk * 10;
if (k % mark == 0)
ans = markk - 1 + ys;
else
ans = markk + ys;
ull ws = k % mark;
string str = to_string(ans);
if(ws!=0)cout << str[ws-1] << endl;
else cout<<str[str.length()-1]<<endl;
}
int main() {
int T; ull q;
pre();//co();
cin >> T;
while (T--)
{
cin >> q;
solve(q);
}
return 0;
}