B.Prefix Code
題意:
給n個長度不超過10的數字串,問是否存在一個串是另一個串的前綴,如果不存在則輸出Yes,如果存在就輸出No
數據範圍:n<=1e4
思路:
字典樹。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
struct Node{
int a[maxm*10][10],tot;
int cnt[maxm*10];//記錄node被經過次數
int is[maxm*10];//記錄串結尾
void init(){
for(int i=0;i<=tot;i++){
memset(a[i],0,sizeof a[i]);
cnt[i]=is[i]=0;
}
tot=0;
}
void add(char *s){
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
cnt[node]++;
}
is[node]=1;
}
int ask(){
for(int i=1;i<=tot;i++){
if(is[i]&&cnt[i]>1)return 1;
}
return 0;
}
}t;
char s[15];
signed main(){
int T;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
t.init();
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
t.add(s);
}
printf("Case #%d: ",cas);
if(!t.ask())puts("Yes");
else puts("No");
}
return 0;
}
D.Spanning Tree Removal
題意:
給n,表示有一個n個頂點的完全圖
每次操作要從中刪除一個生成樹,問做多刪除幾次
且輸出邊的刪除方案
數據範圍:n<=1e3
思路:
因爲完全圖一共n(n-1)/2條邊,一顆生成樹n-1條邊,容易想到可以刪除n/2次
這題的難點主要在於如何構造刪邊方案。
做法:
把n個點圍成一個圓,然後按Z字形刪,這樣刪的話圓周的邊每次只減少兩條,一共n/2次減完
找一下每次刪除的點的規律:
假設起始點爲x,則第一次刪的是x-(x+1),第二次刪的是(x+1)-(x-2),第三次是(x-2)-(x+3)…
發現就是當前點先順時針移動1格,然後逆時針移動兩格,然後順時針移動三格…代碼裏面有體現
code:
#include<bits/stdc++.h>
using namespace std;
signed main(){
int T;
scanf("%d",&T);
int cas=1;
while(T--){
int n;
scanf("%d",&n);
printf("Case #%d: %d\n",cas++,n/2);
for(int i=1;i<=n/2;i++){
int now=i;
int nt;
for(int j=1;j<=n-1;j++){
if(j&1){
nt=(now+j)%n;
}else{
nt=(now-j+n)%n;
}
if(!nt)nt=n;
printf("%d %d\n",now,nt);
now=nt;
}
}
}
return 0;
}
E.Cave Escape
題意:
大意:
給n和m表示有一個n*m的矩陣
給起點(stx,sty)和終點(edx,edy)
每個點(x,y)有權值v(x,y),
從每個點(i,j)可以走到相鄰的點(x,y),如果是第一次走到,那麼總得分會增加兩點的權值積v(i,j)*v(x,y)
現在要從起點走到終點,問路徑上最大得分是多少,注意到達終點之後可以繼續走賺得分,然後再回來
數據範圍:T組數據(T<=10),n,m<=1e3,0<=點權<100
思路:
因爲點權非負,所以走過所有的點才能得到最大得分,因此起點和終點其實是沒用的。
因爲每個點只有第一次到的時候纔會有得分,相鄰格子之間建邊,很容易看出來答案其實就是最大生成樹
但是總點數1e6,邊大概2e6,排序大約4e7,而且是多組數據,有點難頂,很多人都是1700ms左右卡過去的(時限2s)
正解是從數據範圍入手,因爲點權最大100,那麼邊權最大1e4,用類似桶的方法來存邊,避免排序
具體做法是用vector+pair開的g(x)存邊權爲x的邊,因爲邊權最大1e4,因此開1e4大小就行了,從大到小遍歷,不需要排序
最大生成樹就是常規做法,關鍵在於用桶存邊減少複雜度
總結:當需要給邊排序且邊權較小時,可以用桶來存邊避免排序
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=1e6+5;
vector<pair<int,int> >g[10000+5];
int pre[maxm];
int x[maxm];
int n,m,stx,sty,edx,edy;
int A,B,C,P;
int ffind(int x){
return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
int id(int i,int j){
return (i-1)*m+j;
}
signed main(){
int T;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
scanf("%d%d%d%d%d%d",&n,&m,&stx,&sty,&edx,&edy);
scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
printf("Case #%d: ",cas);
for(int i=3;i<=n*m;i++){
x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
}
for(int i=0;i<=10000;i++)g[i].clear();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i+1<=n){
int a=id(i,j);
int b=id(i+1,j);
g[x[a]*x[b]].push_back({a,b});
}
if(j+1<=m){
int a=id(i,j);
int b=id(i,j+1);
g[x[a]*x[b]].push_back({a,b});
}
}
}
for(int i=1;i<=n*m;i++)pre[i]=i;
int tot=0;
ll ans=0;
for(int i=10000;i>=0;i--){
for(auto v:g[i]){
int x=ffind(v.first);
int y=ffind(v.second);
if(x!=y){
ans+=i;
pre[x]=y;
tot++;
}
}
if(tot==n*m-1)break;
}
printf("%lld\n",ans);
}
return 0;
}
F.A Simple Problem On A Tree
題意:
給一棵n個節點的樹,有q次操作:
操作有4種:
1.將u,v路徑上的點權改爲w
2.將u,v路徑上的點權加上w
3.將u,v路徑上的點權乘上w
4.查詢u,v路徑上的點權立方和
思路:
顯然樹剖,線段樹維護:和,平方和,立方和,乘法標記和加法標記。
H.Tree Partition
題意:
給一顆n個頂點的樹,和一個整數k
每個點有點權
要求切斷k-1條邊,將樹分成k個連通塊
每個連通塊的權值爲連通塊內的點的點權和
問最優切割下,最大連通塊的權值的最小值是多少
數據範圍:n<=1e5,k<=n
思路:
最大值最小化要往二分的方向想
顯然最大連通塊的權值滿足單調性
因此二分枚舉最大連通塊的權值,判斷能否成立即可
如何判斷權值mid能否成立:
對於某個幾點,先將所有子節點所在連通塊全部選中,如果總和大於mid,則必須切割
給子節點連通塊從大到小排序,貪心地優先切割權值大的子連通塊,切割的時候記錄切割次數
如果切割總次數小於等於k-1則滿足條件
詳見代碼
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],tot;
int a[maxm];
int n,k;
int mid;
int kk;
void init(int n){
for(int i=1;i<=n;i++)head[i]=0;
tot=0;
}
void add(int x,int y){
tot++;nt[tot]=head[x];head[x]=tot;to[tot]=y;
}
bool cmp(int a,int b){
return a>b;
}
int dfs(int x,int fa){
int sum=a[x];
vector<int>temp;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(v==fa)continue;
int t=dfs(v,x);
temp.push_back(t);
sum+=t;
}
if(sum<=mid)return sum;
sort(temp.begin(),temp.end(),cmp);
for(int v:temp){//貪心的從大到小切割
sum-=v;
kk++;
if(sum<=mid)break;
}
return sum;
}
bool check(){
kk=0;
dfs(1,-1);
return kk<=k-1;
}
signed main(){
int T;
cin>>T;
signed cas=1;
while(T--){
cin>>n>>k;
init(n);
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
int sum=0;
int ma=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
ma=max(ma,a[i]);
}
int ans=0;
int l=ma,r=sum;
while(l<=r){
mid=(l+r)/2;
if(check())ans=mid,r=mid-1;
else l=mid+1;
}
printf("Case #%d: ",cas++);
cout<<ans<<endl;
}
return 0;
}
K.Color Graph
題意:
給一個n個點m條邊的簡單圖,簡單圖的定義是無向圖,沒有自環和重邊。
現在你要選出來一些邊,滿足選出來的邊不構成奇環。
問最多可以選擇多少條邊。
數據範圍:n<=16,m<=n*(n-1)/2
思路:
無奇環的圖是二分圖,二進制枚舉點集的左半部,利用左半部推出右半部,對邊數取max就是答案。
因爲最優解一定是二分圖,所以這樣枚舉一定會枚舉到答案。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=20;
vector<int>g[maxm];
int mark[maxm];
signed main(){
int T;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)g[i].clear();
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
a--,b--;//把下標改成從0開始
g[a].push_back(b);
g[b].push_back(a);
}
int ans=0;
for(int i=0;i<(1<<n);i++){//二進制枚舉左半部
for(int j=0;j<n;j++){//標記選中的點
mark[j]=(i>>j&1);
}
int temp=0;
for(int j=0;j<n;j++){//計算邊
if(i>>j&1){
for(int v:g[j]){
if(!mark[v])temp++;//如果另一端沒被標記說明另一端可以作爲右半部,則邊可選
}
}
}
ans=max(ans,temp);
}
printf("Case #%d: %d\n",cas,ans);
}
return 0;
}